Files
LookingGlass/client/transports/LGMP/audio.c
Geoffrey McRae f2f3787aec [client] audio: reduce LGMP poll reserve
Reduce the active LGMP poll bound and matching playback reserve from
250 us to 125 us.
2026-11-06 14:34:25 +11:00

2353 lines
70 KiB
C

/**
* Looking Glass
* Copyright © 2017-2026 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "audio.h"
#include <LGProtocol/KVMFR.h>
#include "common/debug.h"
#include "common/locking.h"
#include "common/thread.h"
#include "common/time.h"
#include <lgmp/stream.h>
#include <errno.h>
#include <math.h>
#include <stdatomic.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/random.h>
#define AUDIO_IDLE_POLL_MS 20U
#define AUDIO_ACTIVE_POLL_US 125U
#define AUDIO_CLAIM_RETRY_US 200000U
#define AUDIO_OWNER_RETRY_US 1200000U
#define AUDIO_DRAIN_MAX 32U
#define AUDIO_CAPABILITIES \
(KVMFR_AUDIO_CAP_PLAYBACK | KVMFR_AUDIO_CAP_CAPTURE | \
KVMFR_AUDIO_CAP_CLOCK_FEEDBACK)
#define AUDIO_STATUS_FLAGS \
(KVMFR_AUDIO_STATUS_AVAILABLE | KVMFR_AUDIO_STATUS_HAS_OWNER | \
KVMFR_AUDIO_STATUS_PLAYBACK_AVAILABLE | \
KVMFR_AUDIO_STATUS_CAPTURE_AVAILABLE | \
KVMFR_AUDIO_STATUS_PLAYBACK_ACTIVE | \
KVMFR_AUDIO_STATUS_CAPTURE_ACTIVE)
#define AUDIO_CLOCK_FLAGS \
(KVMFR_AUDIO_MESSAGE_CLOCK_VALID | \
KVMFR_AUDIO_MESSAGE_CLOCK_STABLE | \
KVMFR_AUDIO_MESSAGE_DISCONTINUITY)
#define AUDIO_DATA_FLAGS \
(AUDIO_CLOCK_FLAGS | KVMFR_AUDIO_MESSAGE_SILENT)
enum AudioPhase
{
AUDIO_PHASE_IDLE,
AUDIO_PHASE_CLAIM_PENDING,
AUDIO_PHASE_CLAIM_SENT,
AUDIO_PHASE_BOUND,
AUDIO_PHASE_RELEASING,
AUDIO_PHASE_COOLDOWN,
};
struct AudioGeneration
{
uint64_t wire;
uint32_t local;
};
struct AudioStreamState
{
struct AudioGeneration generation;
KVMFRAudioFormat wireFormat;
LG_AudioFormat format;
bool active;
bool formatValid;
bool volumeValid;
bool muteValid;
bool mute;
bool clockValid;
LG_AudioClock clock;
uint8_t volumeChannels;
uint16_t volume[KVMFR_AUDIO_MAX_CHANNELS];
};
struct AudioStatusCallbackFrame
{
const LGMPAudio * audio;
struct AudioStatusCallbackFrame * previous;
};
struct AudioCallbackWaiter
{
struct AudioCallbackWaiter * next;
LGEvent * event;
unsigned depth;
};
struct AudioCallbackWaitQueue
{
LG_Lock lock;
atomic_uint count;
struct AudioCallbackWaiter * waiters;
};
struct LGMPAudio
{
PLGMPClient client;
LGEvent * pointerWake;
LGEvent * event;
LGThread * thread;
LG_Lock lock;
LG_Lock eventLock;
atomic_flag writeGate;
atomic_bool stop;
atomic_bool dataPlaneLost;
atomic_bool captureDiscontinuity;
atomic_bool clockFeedbackAvailable;
atomic_uint statusInFlight;
struct AudioCallbackWaitQueue statusWait;
bool connected;
bool discover;
bool attached;
bool statusAvailable;
bool statusDirty;
bool responsePending;
bool claimPosting;
bool sequenceKnown;
bool playbackDiscontinuity;
bool releaseRequested;
bool releaseSent;
bool hostReadActive;
bool clientWriteActive;
enum AudioPhase phase;
uint32_t clientID;
uint32_t requestSerial;
uint32_t requestGeneration;
unsigned claimSends;
uint64_t claimToken;
uint64_t nextClaimAt;
uint64_t releaseDeadline;
uint64_t nextKeepalive;
uint64_t nextAttachAt;
uint64_t expectedSequence;
uint32_t generationSerial;
struct AudioGeneration endpointGeneration;
KVMFRAudioClaim claim;
KVMFRAudioStreamResponse response;
KVMFRAudioStatus remoteStatus;
struct AudioStreamState playback;
struct AudioStreamState capture;
PLGMPClientStream hostToClientStream;
PLGMPClientStream clientToHostStream;
LGMPStreamPollState streamPoll;
/* Protected by writeGate. */
uint64_t clientSequence;
bool writeBound;
bool publicDataAllowed;
uint64_t writeCaptureGeneration;
uint32_t writeCaptureLocalGeneration;
KVMFRAudioFormat writeCaptureFormat;
uint64_t writePlaybackGeneration;
uint32_t writePlaybackLocalGeneration;
const LG_AudioEventOps * events;
void * eventOpaque;
LG_AudioStatusFn statusCallback;
void * statusOpaque;
};
static _Thread_local struct AudioStatusCallbackFrame * l_statusFrames;
_Static_assert(sizeof(struct LGMPStreamDescriptor) ==
sizeof(KVMFRStreamDescriptor),
"LGMP and KVMFR stream descriptor sizes differ");
_Static_assert(offsetof(struct LGMPStreamDescriptor, offset) ==
offsetof(KVMFRStreamDescriptor, offset),
"LGMP and KVMFR stream descriptor offsets differ");
_Static_assert(offsetof(struct LGMPStreamDescriptor, slotSize) ==
offsetof(KVMFRStreamDescriptor, slotSize),
"LGMP and KVMFR stream descriptor geometry differs");
_Static_assert(KVMFR_AUDIO_MAX_CHANNELS <= LG_AUDIO_MAX_CHANNELS,
"client audio cannot represent every KVMFR speaker position");
static uint32_t nextNonzero(uint32_t * value)
{
if (++*value == 0)
++*value;
return *value;
}
static bool randomBytes(void * buffer, size_t size)
{
uint8_t * output = buffer;
while (size)
{
const ssize_t received = getrandom(output, size, 0);
if (received < 0 && errno == EINTR)
continue;
if (received <= 0)
return false;
output += (size_t)received;
size -= (size_t)received;
}
return true;
}
static bool tryWriteGate(LGMPAudio * audio)
{
return !atomic_flag_test_and_set_explicit(
&audio->writeGate, memory_order_acquire);
}
static void lockWriteGate(LGMPAudio * audio)
{
while (!tryWriteGate(audio))
{
}
}
static void unlockWriteGate(LGMPAudio * audio)
{
atomic_flag_clear_explicit(&audio->writeGate, memory_order_release);
}
/* writeGate must be held. */
static void closePublicDataLocked(LGMPAudio * audio)
{
audio->publicDataAllowed = false;
audio->writeCaptureGeneration = 0;
audio->writeCaptureLocalGeneration = 0;
audio->writePlaybackGeneration = 0;
audio->writePlaybackLocalGeneration = 0;
memset(&audio->writeCaptureFormat, 0,
sizeof(audio->writeCaptureFormat));
}
static void signalWorker(LGMPAudio * audio)
{
lgSignalEvent(audio->event);
}
static unsigned statusCallbackDepth(const LGMPAudio * audio)
{
unsigned depth = 0;
for (const struct AudioStatusCallbackFrame * frame = l_statusFrames;
frame; frame = frame->previous)
if (frame->audio == audio)
++depth;
return depth;
}
static void signalCallbackWaiters(
struct AudioCallbackWaitQueue * queue, unsigned remaining)
{
if (!atomic_load_explicit(&queue->count, memory_order_acquire))
return;
LG_LOCK(queue->lock);
struct AudioCallbackWaiter ** link = &queue->waiters;
while (*link)
{
struct AudioCallbackWaiter * waiter = *link;
if (remaining > waiter->depth)
{
link = &waiter->next;
continue;
}
*link = waiter->next;
atomic_fetch_sub_explicit(
&queue->count, 1, memory_order_release);
lgSignalEvent(waiter->event);
}
LG_UNLOCK(queue->lock);
}
/* audio->lock must protect callback admission. */
static void beginStatusCallback(LGMPAudio * audio,
struct AudioStatusCallbackFrame * frame)
{
atomic_fetch_add_explicit(
&audio->statusInFlight, 1, memory_order_relaxed);
frame->audio = audio;
frame->previous = l_statusFrames;
l_statusFrames = frame;
}
static void endStatusCallback(LGMPAudio * audio,
const struct AudioStatusCallbackFrame * frame)
{
l_statusFrames = frame->previous;
const unsigned previous = atomic_fetch_sub_explicit(
&audio->statusInFlight, 1, memory_order_release);
DEBUG_ASSERT(previous > 0);
signalCallbackWaiters(&audio->statusWait, previous - 1);
}
static void waitStatusCallbacks(LGMPAudio * audio)
{
const unsigned depth = statusCallbackDepth(audio);
if (atomic_load_explicit(
&audio->statusInFlight, memory_order_acquire) <= depth)
return;
struct AudioCallbackWaiter waiter =
{
.event = lgCreateEvent(true, 0),
.depth = depth,
};
if (!waiter.event)
DEBUG_FATAL("Failed to create LGMP audio callback wait event");
bool queued = false;
LG_LOCK(audio->statusWait.lock);
atomic_fetch_add_explicit(
&audio->statusWait.count, 1, memory_order_release);
if (atomic_load_explicit(
&audio->statusInFlight, memory_order_acquire) > depth)
{
waiter.next = audio->statusWait.waiters;
audio->statusWait.waiters = &waiter;
queued = true;
}
else
atomic_fetch_sub_explicit(
&audio->statusWait.count, 1, memory_order_release);
LG_UNLOCK(audio->statusWait.lock);
if (queued)
{
if (!lgWaitEvent(waiter.event, TIMEOUT_INFINITE))
DEBUG_FATAL("Failed to wait for LGMP audio status callbacks");
LG_LOCK(audio->statusWait.lock);
LG_UNLOCK(audio->statusWait.lock);
}
lgFreeEvent(waiter.event);
}
static unsigned channelCount(uint32_t mask)
{
unsigned count = 0;
while (mask)
{
count += mask & 1U;
mask >>= 1;
}
return count;
}
static LG_AudioChannel channelFromBit(unsigned bit)
{
static const LG_AudioChannel channels[KVMFR_AUDIO_MAX_CHANNELS] =
{
LG_AUDIO_CH_FRONT_LEFT,
LG_AUDIO_CH_FRONT_RIGHT,
LG_AUDIO_CH_FRONT_CENTER,
LG_AUDIO_CH_LFE,
LG_AUDIO_CH_REAR_LEFT,
LG_AUDIO_CH_REAR_RIGHT,
LG_AUDIO_CH_FRONT_LEFT_CENTER,
LG_AUDIO_CH_FRONT_RIGHT_CENTER,
LG_AUDIO_CH_REAR_CENTER,
LG_AUDIO_CH_SIDE_LEFT,
LG_AUDIO_CH_SIDE_RIGHT,
LG_AUDIO_CH_TOP_CENTER,
LG_AUDIO_CH_TOP_FRONT_LEFT,
LG_AUDIO_CH_TOP_FRONT_CENTER,
LG_AUDIO_CH_TOP_FRONT_RIGHT,
LG_AUDIO_CH_TOP_REAR_LEFT,
LG_AUDIO_CH_TOP_REAR_CENTER,
LG_AUDIO_CH_TOP_REAR_RIGHT,
};
return bit < KVMFR_AUDIO_MAX_CHANNELS ?
channels[bit] : LG_AUDIO_CH_UNKNOWN;
}
static bool validFormat(const KVMFRAudioFormat * wire,
LG_AudioFormat * result)
{
if (!wire || wire->version != KVMFR_AUDIO_FORMAT_VERSION ||
wire->size != sizeof(*wire) || !wire->sampleRate ||
wire->sampleRate > KVMFR_AUDIO_MAX_SAMPLE_RATE ||
!wire->channelMask ||
(wire->channelMask & ~KVMFR_AUDIO_CHANNEL_POSITION_MASK) ||
!wire->channelCount ||
wire->channelCount > KVMFR_AUDIO_STREAM_MAX_CHANNELS ||
channelCount(wire->channelMask) != wire->channelCount ||
wire->reserved[0] || wire->reserved[1])
return false;
LG_AudioSampleFormat format;
unsigned bytesPerSample;
switch (wire->sampleFormat)
{
case KVMFR_AUDIO_SAMPLE_S16_LE:
format = LG_AUDIO_FMT_S16_LE;
bytesPerSample = 2;
if (wire->validBits != 16 || wire->containerBits != 16)
return false;
break;
case KVMFR_AUDIO_SAMPLE_S24_LE:
format = LG_AUDIO_FMT_S24_LE;
bytesPerSample = 3;
if (wire->validBits != 24 || wire->containerBits != 24)
return false;
break;
default:
return false;
}
if (wire->blockAlign != wire->channelCount * bytesPerSample)
return false;
*result = (LG_AudioFormat)
{
.sampleFormat = format,
.sampleRate = wire->sampleRate,
.channelCount = (uint8_t)wire->channelCount,
};
unsigned channel = 0;
for (unsigned bit = 0; bit < KVMFR_AUDIO_MAX_CHANNELS; ++bit)
if (wire->channelMask & (UINT32_C(1) << bit))
result->channels[channel++] = channelFromBit(bit);
return true;
}
static bool sameFormat(const KVMFRAudioFormat * left,
const KVMFRAudioFormat * right)
{
return !memcmp(left, right, sizeof(*left));
}
static bool emptyFormat(const KVMFRAudioFormat * format)
{
const KVMFRAudioFormat empty = { 0 };
return !memcmp(format, &empty, sizeof(empty));
}
static bool validClockFlags(uint32_t flags)
{
return !(flags & ~AUDIO_CLOCK_FLAGS) &&
(!(flags & KVMFR_AUDIO_MESSAGE_CLOCK_STABLE) ||
(flags & KVMFR_AUDIO_MESSAGE_CLOCK_VALID));
}
static LG_AudioClock clockFromMessage(const KVMFRAudioMessage * message,
bool discontinuity)
{
return (LG_AudioClock)
{
.position = message->position,
.time = message->timeNs,
.rate = (double)message->rateQ32 / 4294967296.0,
.stable =
(message->flags & KVMFR_AUDIO_MESSAGE_CLOCK_STABLE) != 0,
.discontinuity = discontinuity ||
(message->flags & KVMFR_AUDIO_MESSAGE_DISCONTINUITY) != 0,
};
}
static uint32_t mapGeneration(LGMPAudio * audio,
struct AudioGeneration * generation, uint64_t wire)
{
if (!wire)
return 0;
if (generation->wire != wire || !generation->local)
{
generation->wire = wire;
generation->local = nextNonzero(&audio->generationSerial);
}
return generation->local;
}
static struct LGMPStreamDescriptor descriptorFromWire(
const KVMFRStreamDescriptor * wire)
{
return (struct LGMPStreamDescriptor)
{
.magic = wire->magic,
.version = wire->version,
.size = wire->size,
.offset = wire->offset,
.regionSize = wire->regionSize,
.direction = wire->direction,
.policy = wire->policy,
.slotCount = wire->slotCount,
.slotSize = wire->slotSize,
};
}
static bool validDescriptor(const KVMFRStreamDescriptor * descriptor,
enum LGMPStreamDirection direction)
{
return descriptor->magic == LGMP_STREAM_DESCRIPTOR_MAGIC &&
descriptor->version == LGMP_STREAM_DESCRIPTOR_VERSION &&
descriptor->size == sizeof(*descriptor) && descriptor->offset &&
descriptor->regionSize &&
descriptor->direction == (uint32_t)direction &&
descriptor->policy == LGMP_STREAM_RELIABLE_FIFO &&
descriptor->slotCount == KVMFR_AUDIO_STREAM_SLOT_COUNT &&
descriptor->slotSize == KVMFR_AUDIO_STREAM_SLOT_BYTES;
}
static void markStatusNL(LGMPAudio * audio, bool available,
uint64_t endpointGeneration)
{
const uint32_t oldGeneration = audio->endpointGeneration.local;
if (endpointGeneration)
mapGeneration(audio, &audio->endpointGeneration, endpointGeneration);
if (audio->statusAvailable != available ||
oldGeneration != audio->endpointGeneration.local)
audio->statusDirty = true;
audio->statusAvailable = available;
atomic_store_explicit(&audio->clockFeedbackAvailable,
available && (audio->remoteStatus.capabilities &
KVMFR_AUDIO_CAP_CLOCK_FEEDBACK), memory_order_release);
}
static void publishStatus(LGMPAudio * audio)
{
LG_LOCK(audio->lock);
if (!audio->statusDirty)
{
LG_UNLOCK(audio->lock);
return;
}
audio->statusDirty = false;
const LG_AudioStatusFn callback = audio->statusCallback;
void * opaque = audio->statusOpaque;
const LG_AudioStatus status =
{
.available = audio->connected && audio->statusAvailable,
.generation = audio->endpointGeneration.local,
};
struct AudioStatusCallbackFrame frame;
if (callback)
beginStatusCallback(audio, &frame);
LG_UNLOCK(audio->lock);
if (callback)
{
callback(opaque, &status);
endStatusCallback(audio, &frame);
}
}
static void updateWriteStreamState(LGMPAudio * audio)
{
lockWriteGate(audio);
LG_LOCK(audio->lock);
const bool admitted = audio->writeBound &&
audio->phase == AUDIO_PHASE_BOUND && !audio->releaseRequested;
const uint64_t captureGeneration = admitted && audio->capture.active ?
audio->capture.generation.wire : 0;
const uint32_t captureLocalGeneration =
admitted && audio->capture.active ?
audio->capture.generation.local : 0;
const bool captureChanged =
audio->writeCaptureGeneration != captureGeneration ||
audio->writeCaptureLocalGeneration != captureLocalGeneration;
audio->writeCaptureGeneration = captureGeneration;
audio->writeCaptureLocalGeneration = captureLocalGeneration;
audio->writeCaptureFormat = audio->capture.wireFormat;
audio->writePlaybackGeneration = admitted && audio->playback.active ?
audio->playback.generation.wire : 0;
audio->writePlaybackLocalGeneration =
admitted && audio->playback.active ?
audio->playback.generation.local : 0;
audio->publicDataAllowed = admitted &&
(audio->writeCaptureGeneration || audio->writePlaybackGeneration);
LG_UNLOCK(audio->lock);
if (captureChanged)
atomic_store_explicit(
&audio->captureDiscontinuity, false, memory_order_release);
unlockWriteGate(audio);
}
static void stopActiveStreams(LGMPAudio * audio)
{
const LG_AudioEventOps * events;
void * opaque;
uint32_t playbackGeneration;
uint32_t captureGeneration;
LG_LOCK(audio->eventLock);
LG_LOCK(audio->lock);
events = audio->attached ? audio->events : NULL;
opaque = audio->eventOpaque;
playbackGeneration = audio->playback.active ?
audio->playback.generation.local : 0;
captureGeneration = audio->capture.active ?
audio->capture.generation.local : 0;
memset(&audio->playback, 0, sizeof(audio->playback));
memset(&audio->capture, 0, sizeof(audio->capture));
audio->playbackDiscontinuity = false;
LG_UNLOCK(audio->lock);
updateWriteStreamState(audio);
if (events && playbackGeneration && events->playbackStop)
events->playbackStop(opaque, playbackGeneration);
if (events && captureGeneration && events->recordStop)
events->recordStop(opaque, captureGeneration);
LG_UNLOCK(audio->eventLock);
}
static void detachStreams(LGMPAudio * audio)
{
lockWriteGate(audio);
audio->publicDataAllowed = false;
audio->writeBound = false;
audio->writeCaptureGeneration = 0;
audio->writeCaptureLocalGeneration = 0;
audio->writePlaybackGeneration = 0;
audio->writePlaybackLocalGeneration = 0;
memset(&audio->writeCaptureFormat, 0,
sizeof(audio->writeCaptureFormat));
lgmpClientStreamDetach(&audio->clientToHostStream);
lgmpClientStreamDetach(&audio->hostToClientStream);
audio->hostReadActive = false;
audio->clientWriteActive = false;
audio->clientSequence = 0;
atomic_store_explicit(
&audio->captureDiscontinuity, false, memory_order_release);
unlockWriteGate(audio);
}
static void scheduleRetryNL(LGMPAudio * audio, uint64_t delay)
{
audio->phase = AUDIO_PHASE_IDLE;
audio->requestGeneration = 0;
audio->claimToken = 0;
audio->claimSends = 0;
audio->claimPosting = false;
audio->releaseRequested = false;
audio->releaseSent = false;
memset(&audio->claim, 0, sizeof(audio->claim));
audio->nextClaimAt = microtime() + delay;
}
static void finishBinding(LGMPAudio * audio, uint64_t delay)
{
stopActiveStreams(audio);
detachStreams(audio);
atomic_store_explicit(
&audio->dataPlaneLost, false, memory_order_release);
LG_LOCK(audio->lock);
memset(&audio->remoteStatus, 0, sizeof(audio->remoteStatus));
audio->sequenceKnown = false;
audio->expectedSequence = 0;
audio->releaseRequested = false;
audio->releaseSent = false;
audio->responsePending = false;
markStatusNL(audio, false, 0);
audio->endpointGeneration.wire = 0;
if (audio->connected && audio->discover)
scheduleRetryNL(audio, delay);
else
{
audio->phase = AUDIO_PHASE_IDLE;
audio->requestGeneration = 0;
audio->claimToken = 0;
audio->claimSends = 0;
audio->claimPosting = false;
}
LG_UNLOCK(audio->lock);
if (audio->pointerWake)
lgSignalEvent(audio->pointerWake);
}
static bool attachStreams(LGMPAudio * audio,
const KVMFRAudioStreamResponse * response)
{
if (!validDescriptor(&response->hostToClient,
LGMP_STREAM_HOST_TO_CLIENT) ||
!validDescriptor(&response->clientToHost,
LGMP_STREAM_CLIENT_TO_HOST))
{
DEBUG_WARN("Invalid LGMP audio stream descriptors");
return false;
}
LGMP_STATUS hostStatus = LGMP_OK;
if (!audio->hostToClientStream)
{
const struct LGMPStreamDescriptor hostToClient =
descriptorFromWire(&response->hostToClient);
hostStatus = lgmpClientStreamAttach(audio->client,
&hostToClient, &audio->hostToClientStream);
}
if (hostStatus == LGMP_OK)
{
uint32_t epoch = 0;
hostStatus = lgmpClientStreamActivate(
audio->hostToClientStream, &epoch);
audio->hostReadActive = hostStatus == LGMP_OK;
}
if (hostStatus != LGMP_OK)
DEBUG_WARN("Failed to attach/activate host-to-client audio stream: %s",
lgmpStatusString(hostStatus));
bool hostOwner = audio->hostReadActive;
if (hostOwner)
{
uint32_t clientID = 0;
uint32_t epoch = 0;
hostOwner = lgmpClientStreamGetBinding(
audio->hostToClientStream, &clientID, &epoch) == LGMP_OK &&
clientID == audio->clientID && epoch;
}
lockWriteGate(audio);
LGMP_STATUS clientStatus = LGMP_OK;
if (!audio->clientToHostStream)
{
const struct LGMPStreamDescriptor clientToHost =
descriptorFromWire(&response->clientToHost);
clientStatus = lgmpClientStreamAttach(audio->client,
&clientToHost, &audio->clientToHostStream);
}
if (clientStatus == LGMP_OK)
{
uint32_t epoch = 0;
clientStatus = lgmpClientStreamActivate(
audio->clientToHostStream, &epoch);
audio->clientWriteActive = clientStatus == LGMP_OK;
}
bool clientOwner = audio->clientWriteActive;
if (clientOwner)
{
uint32_t clientID = 0;
uint32_t epoch = 0;
clientOwner = lgmpClientStreamGetBinding(
audio->clientToHostStream, &clientID, &epoch) == LGMP_OK &&
clientID == audio->clientID && epoch;
}
audio->clientWriteActive = clientOwner;
const bool wasWriteBound = audio->writeBound;
audio->writeBound = clientOwner;
if (audio->writeBound && !wasWriteBound)
audio->clientSequence = 0;
unlockWriteGate(audio);
if (clientStatus != LGMP_OK)
DEBUG_WARN("Failed to attach/activate client-to-host audio stream: %s",
lgmpStatusString(clientStatus));
audio->hostReadActive = hostOwner;
lgmpStreamPollActivity(&audio->streamPoll);
return hostOwner && clientOwner;
}
static bool parseMessage(const LGMPStreamBuffer * buffer,
KVMFRAudioMessage * message, const uint8_t ** payload)
{
if (!buffer->data || buffer->size < sizeof(*message) ||
buffer->size > buffer->capacity)
return false;
memcpy(message, buffer->data, sizeof(*message));
if (message->version != KVMFR_AUDIO_MESSAGE_VERSION ||
message->size != sizeof(*message) || message->reserved0 ||
message->length > KVMFR_AUDIO_PCM_BYTES ||
buffer->size != sizeof(*message) + message->length)
return false;
*payload = (const uint8_t *)buffer->data + sizeof(*message);
return true;
}
static void requestRelease(LGMPAudio * audio, const char * reason)
{
bool log = false;
lockWriteGate(audio);
LG_LOCK(audio->lock);
closePublicDataLocked(audio);
if (!audio->releaseRequested)
{
audio->releaseRequested = true;
audio->releaseDeadline =
microtime() + AUDIO_OWNER_RETRY_US;
markStatusNL(audio, false, 0);
log = true;
}
if (audio->phase == AUDIO_PHASE_BOUND)
audio->phase = AUDIO_PHASE_RELEASING;
LG_UNLOCK(audio->lock);
unlockWriteGate(audio);
if (log)
DEBUG_INFO("LGMP audio ownership releasing: %s", reason);
signalWorker(audio);
}
static bool validEmptyMessage(const KVMFRAudioMessage * message,
uint32_t allowedFlags)
{
return !(message->flags & ~allowedFlags) &&
!message->position && !message->timeNs && !message->rateQ32 &&
!message->frames && !message->length;
}
static bool validVolume(const KVMFRAudioVolume * volume,
uint8_t channels)
{
if (volume->version != KVMFR_AUDIO_VOLUME_VERSION ||
volume->size != sizeof(*volume) || volume->reserved ||
volume->channelCount != channels)
return false;
for (unsigned i = channels; i < KVMFR_AUDIO_MAX_CHANNELS; ++i)
if (volume->values[i])
return false;
return true;
}
static bool validStatus(const LGMPAudio * audio,
const KVMFRAudioStatus * status)
{
if (status->version != KVMFR_AUDIO_STATUS_VERSION ||
status->size != sizeof(*status) ||
(status->flags & ~AUDIO_STATUS_FLAGS) ||
status->protocolVersion != KVMFR_AUDIO_PROTOCOL_MAX ||
status->requestGeneration != audio->requestGeneration ||
(status->capabilities & ~AUDIO_CAPABILITIES) ||
status->ownerClientID != audio->clientID ||
status->ownerGeneration != audio->requestGeneration ||
!(status->flags & KVMFR_AUDIO_STATUS_HAS_OWNER) ||
status->reserved0 || !status->endpointGeneration ||
status->playbackState > KVMFR_AUDIO_STREAM_RUNNING ||
status->captureState > KVMFR_AUDIO_STREAM_RUNNING)
return false;
const bool playbackAvailable =
(status->flags & KVMFR_AUDIO_STATUS_PLAYBACK_AVAILABLE) != 0;
const bool captureAvailable =
(status->flags & KVMFR_AUDIO_STATUS_CAPTURE_AVAILABLE) != 0;
if (playbackAvailable !=
((status->capabilities & KVMFR_AUDIO_CAP_PLAYBACK) != 0) ||
captureAvailable !=
((status->capabilities & KVMFR_AUDIO_CAP_CAPTURE) != 0))
return false;
const bool playbackActive =
(status->flags & KVMFR_AUDIO_STATUS_PLAYBACK_ACTIVE) != 0;
const bool captureActive =
(status->flags & KVMFR_AUDIO_STATUS_CAPTURE_ACTIVE) != 0;
const bool available =
(status->flags & KVMFR_AUDIO_STATUS_AVAILABLE) != 0;
LG_AudioFormat ignored;
if (playbackActive &&
(!available || !playbackAvailable || !status->playbackGeneration ||
status->playbackState == KVMFR_AUDIO_STREAM_STOPPED ||
!validFormat(&status->playbackFormat, &ignored)))
return false;
if (captureActive &&
(!available || !captureAvailable || !status->captureGeneration ||
status->captureState == KVMFR_AUDIO_STREAM_STOPPED ||
!validFormat(&status->captureFormat, &ignored)))
return false;
if ((!emptyFormat(&status->playbackFormat) &&
!validFormat(&status->playbackFormat, &ignored)) ||
(!emptyFormat(&status->captureFormat) &&
!validFormat(&status->captureFormat, &ignored)))
return false;
return true;
}
static bool currentPlaybackNL(const LGMPAudio * audio,
uint64_t wireGeneration)
{
return audio->playback.active && wireGeneration &&
audio->playback.generation.wire == wireGeneration;
}
static bool currentCaptureNL(const LGMPAudio * audio,
uint64_t wireGeneration)
{
return audio->capture.active && wireGeneration &&
audio->capture.generation.wire == wireGeneration;
}
static void dispatchPlaybackStop(LGMPAudio * audio,
uint64_t wireGeneration)
{
LG_LOCK(audio->eventLock);
LG_LOCK(audio->lock);
if (!currentPlaybackNL(audio, wireGeneration))
{
LG_UNLOCK(audio->lock);
LG_UNLOCK(audio->eventLock);
return;
}
const uint32_t generation = audio->playback.generation.local;
const LG_AudioEventOps * events =
audio->attached ? audio->events : NULL;
void * opaque = audio->eventOpaque;
memset(&audio->playback, 0, sizeof(audio->playback));
audio->playbackDiscontinuity = false;
LG_UNLOCK(audio->lock);
updateWriteStreamState(audio);
if (events && events->playbackStop)
events->playbackStop(opaque, generation);
LG_UNLOCK(audio->eventLock);
}
static void dispatchCaptureStop(LGMPAudio * audio,
uint64_t wireGeneration)
{
LG_LOCK(audio->eventLock);
LG_LOCK(audio->lock);
if (!currentCaptureNL(audio, wireGeneration))
{
LG_UNLOCK(audio->lock);
LG_UNLOCK(audio->eventLock);
return;
}
const uint32_t generation = audio->capture.generation.local;
const LG_AudioEventOps * events =
audio->attached ? audio->events : NULL;
void * opaque = audio->eventOpaque;
memset(&audio->capture, 0, sizeof(audio->capture));
LG_UNLOCK(audio->lock);
updateWriteStreamState(audio);
if (events && events->recordStop)
events->recordStop(opaque, generation);
LG_UNLOCK(audio->eventLock);
}
static void dispatchPlaybackStart(LGMPAudio * audio,
const KVMFRAudioMessage * message, const KVMFRAudioFormat * wire,
const LG_AudioFormat * format, bool sequenceGap)
{
LG_LOCK(audio->eventLock);
LG_LOCK(audio->lock);
const uint32_t oldGeneration = audio->playback.active ?
audio->playback.generation.local : 0;
const uint32_t generation = mapGeneration(audio,
&audio->playback.generation, message->streamGeneration);
const LG_AudioEventOps * events =
audio->attached ? audio->events : NULL;
void * opaque = audio->eventOpaque;
audio->playback.wireFormat = *wire;
audio->playback.format = *format;
audio->playback.active = true;
audio->playback.formatValid = true;
audio->playback.volumeValid = false;
audio->playback.muteValid = false;
audio->playback.clockValid =
(message->flags & KVMFR_AUDIO_MESSAGE_CLOCK_VALID) != 0;
audio->playback.clock = clockFromMessage(message, sequenceGap);
audio->playbackDiscontinuity = sequenceGap ||
(message->flags & KVMFR_AUDIO_MESSAGE_DISCONTINUITY);
LG_UNLOCK(audio->lock);
updateWriteStreamState(audio);
if (events && oldGeneration && oldGeneration != generation &&
events->playbackStop)
events->playbackStop(opaque, oldGeneration);
if (events && events->playbackStart)
{
const bool clockValid =
(message->flags & KVMFR_AUDIO_MESSAGE_CLOCK_VALID) != 0;
const LG_AudioClock clock =
clockFromMessage(message, sequenceGap);
events->playbackStart(opaque, generation, format,
clockValid ? &clock : NULL);
}
LG_UNLOCK(audio->eventLock);
}
static void dispatchCaptureStart(LGMPAudio * audio,
const KVMFRAudioMessage * message, const KVMFRAudioFormat * wire,
const LG_AudioFormat * format)
{
LG_LOCK(audio->eventLock);
LG_LOCK(audio->lock);
const uint32_t oldGeneration = audio->capture.active ?
audio->capture.generation.local : 0;
const uint32_t generation = mapGeneration(audio,
&audio->capture.generation, message->streamGeneration);
const LG_AudioEventOps * events =
audio->attached ? audio->events : NULL;
void * opaque = audio->eventOpaque;
audio->capture.wireFormat = *wire;
audio->capture.format = *format;
audio->capture.active = true;
audio->capture.formatValid = true;
audio->capture.volumeValid = false;
audio->capture.muteValid = false;
LG_UNLOCK(audio->lock);
updateWriteStreamState(audio);
if (events && oldGeneration && oldGeneration != generation &&
events->recordStop)
events->recordStop(opaque, oldGeneration);
if (events && events->recordStart)
events->recordStart(opaque, generation, format);
LG_UNLOCK(audio->eventLock);
}
static void dispatchPlaybackVolume(LGMPAudio * audio,
uint64_t wireGeneration, const KVMFRAudioVolume * volume)
{
LG_LOCK(audio->eventLock);
LG_LOCK(audio->lock);
if (!currentPlaybackNL(audio, wireGeneration))
{
LG_UNLOCK(audio->lock);
LG_UNLOCK(audio->eventLock);
return;
}
const uint32_t generation = audio->playback.generation.local;
const LG_AudioEventOps * events =
audio->attached ? audio->events : NULL;
void * opaque = audio->eventOpaque;
audio->playback.volumeValid = true;
audio->playback.volumeChannels = (uint8_t)volume->channelCount;
memcpy(audio->playback.volume, volume->values,
sizeof(audio->playback.volume));
LG_UNLOCK(audio->lock);
if (events && events->playbackVolume)
events->playbackVolume(opaque, generation,
(uint8_t)volume->channelCount, volume->values);
LG_UNLOCK(audio->eventLock);
}
static void dispatchCaptureVolume(LGMPAudio * audio,
uint64_t wireGeneration, const KVMFRAudioVolume * volume)
{
LG_LOCK(audio->eventLock);
LG_LOCK(audio->lock);
if (!currentCaptureNL(audio, wireGeneration))
{
LG_UNLOCK(audio->lock);
LG_UNLOCK(audio->eventLock);
return;
}
const uint32_t generation = audio->capture.generation.local;
const LG_AudioEventOps * events =
audio->attached ? audio->events : NULL;
void * opaque = audio->eventOpaque;
audio->capture.volumeValid = true;
audio->capture.volumeChannels = (uint8_t)volume->channelCount;
memcpy(audio->capture.volume, volume->values,
sizeof(audio->capture.volume));
LG_UNLOCK(audio->lock);
if (events && events->recordVolume)
events->recordVolume(opaque, generation,
(uint8_t)volume->channelCount, volume->values);
LG_UNLOCK(audio->eventLock);
}
static void dispatchPlaybackMute(LGMPAudio * audio,
uint64_t wireGeneration, bool mute)
{
LG_LOCK(audio->eventLock);
LG_LOCK(audio->lock);
if (!currentPlaybackNL(audio, wireGeneration))
{
LG_UNLOCK(audio->lock);
LG_UNLOCK(audio->eventLock);
return;
}
const uint32_t generation = audio->playback.generation.local;
const LG_AudioEventOps * events =
audio->attached ? audio->events : NULL;
void * opaque = audio->eventOpaque;
audio->playback.muteValid = true;
audio->playback.mute = mute;
LG_UNLOCK(audio->lock);
if (events && events->playbackMute)
events->playbackMute(opaque, generation, mute);
LG_UNLOCK(audio->eventLock);
}
static void dispatchCaptureMute(LGMPAudio * audio,
uint64_t wireGeneration, bool mute)
{
LG_LOCK(audio->eventLock);
LG_LOCK(audio->lock);
if (!currentCaptureNL(audio, wireGeneration))
{
LG_UNLOCK(audio->lock);
LG_UNLOCK(audio->eventLock);
return;
}
const uint32_t generation = audio->capture.generation.local;
const LG_AudioEventOps * events =
audio->attached ? audio->events : NULL;
void * opaque = audio->eventOpaque;
audio->capture.muteValid = true;
audio->capture.mute = mute;
LG_UNLOCK(audio->lock);
if (events && events->recordMute)
events->recordMute(opaque, generation, mute);
LG_UNLOCK(audio->eventLock);
}
static const uint8_t l_silence[KVMFR_AUDIO_PCM_BYTES];
static void dispatchPlaybackData(LGMPAudio * audio,
const KVMFRAudioMessage * message, const uint8_t * payload,
bool sequenceGap)
{
LG_LOCK(audio->eventLock);
LG_LOCK(audio->lock);
if (!currentPlaybackNL(audio, message->streamGeneration))
{
LG_UNLOCK(audio->lock);
LG_UNLOCK(audio->eventLock);
return;
}
const bool silent =
(message->flags & KVMFR_AUDIO_MESSAGE_SILENT) != 0;
const size_t bytes =
(size_t)message->frames * audio->playback.wireFormat.blockAlign;
if (bytes > KVMFR_AUDIO_PCM_BYTES ||
(silent ? message->length != 0 : message->length != bytes))
{
LG_UNLOCK(audio->lock);
LG_UNLOCK(audio->eventLock);
requestRelease(audio, "invalid playback audio length");
return;
}
const uint32_t generation = audio->playback.generation.local;
const LG_AudioEventOps * events =
audio->attached ? audio->events : NULL;
void * opaque = audio->eventOpaque;
const bool discontinuity = sequenceGap ||
audio->playbackDiscontinuity;
const bool clockValid =
(message->flags & KVMFR_AUDIO_MESSAGE_CLOCK_VALID) != 0;
if (!events || !events->playbackData)
audio->playbackDiscontinuity = true;
else
audio->playbackDiscontinuity = false;
LG_UNLOCK(audio->lock);
if (events && events->playbackData)
{
const LG_AudioClock clock =
clockFromMessage(message, discontinuity);
events->playbackData(opaque, generation,
silent ? l_silence : payload, message->frames,
clockValid ? &clock : NULL);
}
LG_UNLOCK(audio->eventLock);
}
static bool processStatus(LGMPAudio * audio,
const KVMFRAudioMessage * message, const uint8_t * payload)
{
if (message->streamGeneration || message->flags ||
message->position || message->timeNs || message->rateQ32 ||
message->frames || message->length != sizeof(KVMFRAudioStatus))
return false;
KVMFRAudioStatus status;
memcpy(&status, payload, sizeof(status));
LG_LOCK(audio->lock);
const bool valid = validStatus(audio, &status);
if (valid)
{
const bool playbackActive =
(status.flags & KVMFR_AUDIO_STATUS_PLAYBACK_ACTIVE) != 0;
const bool captureActive =
(status.flags & KVMFR_AUDIO_STATUS_CAPTURE_ACTIVE) != 0;
if (playbackActive &&
(!currentPlaybackNL(audio, status.playbackGeneration) ||
!sameFormat(&audio->playback.wireFormat,
&status.playbackFormat)))
{
LG_UNLOCK(audio->lock);
return false;
}
if (captureActive &&
(!currentCaptureNL(audio, status.captureGeneration) ||
!sameFormat(&audio->capture.wireFormat,
&status.captureFormat)))
{
LG_UNLOCK(audio->lock);
return false;
}
if ((!playbackActive && audio->playback.active) ||
(!captureActive && audio->capture.active))
{
LG_UNLOCK(audio->lock);
return false;
}
audio->remoteStatus = status;
const bool available =
(status.flags & KVMFR_AUDIO_STATUS_AVAILABLE) != 0;
markStatusNL(audio, available, status.endpointGeneration);
}
LG_UNLOCK(audio->lock);
return valid;
}
static bool processInboundMessage(LGMPAudio * audio,
const KVMFRAudioMessage * message, const uint8_t * payload,
bool sequenceGap)
{
switch (message->type)
{
case KVMFR_AUDIO_MESSAGE_STATUS:
return processStatus(audio, message, payload);
case KVMFR_AUDIO_MESSAGE_PLAYBACK_START:
case KVMFR_AUDIO_MESSAGE_CAPTURE_START:
{
if (!message->streamGeneration || message->frames ||
message->length != sizeof(KVMFRAudioFormat) ||
!validClockFlags(message->flags) ||
(!(message->flags & KVMFR_AUDIO_MESSAGE_CLOCK_VALID) &&
(message->position || message->timeNs || message->rateQ32)))
return false;
KVMFRAudioFormat wire;
LG_AudioFormat format;
memcpy(&wire, payload, sizeof(wire));
if (!validFormat(&wire, &format))
return false;
if (message->type == KVMFR_AUDIO_MESSAGE_PLAYBACK_START)
dispatchPlaybackStart(
audio, message, &wire, &format, sequenceGap);
else
dispatchCaptureStart(audio, message, &wire, &format);
return true;
}
case KVMFR_AUDIO_MESSAGE_PLAYBACK_STOP:
case KVMFR_AUDIO_MESSAGE_CAPTURE_STOP:
if (!message->streamGeneration ||
!validEmptyMessage(message, 0))
return false;
if (message->type == KVMFR_AUDIO_MESSAGE_PLAYBACK_STOP)
dispatchPlaybackStop(audio, message->streamGeneration);
else
dispatchCaptureStop(audio, message->streamGeneration);
return true;
case KVMFR_AUDIO_MESSAGE_PLAYBACK_VOLUME:
case KVMFR_AUDIO_MESSAGE_CAPTURE_VOLUME:
{
if (!message->streamGeneration || message->flags ||
message->position || message->timeNs || message->rateQ32 ||
message->frames ||
message->length != sizeof(KVMFRAudioVolume))
return false;
KVMFRAudioVolume volume;
memcpy(&volume, payload, sizeof(volume));
LG_LOCK(audio->lock);
const bool current =
message->type == KVMFR_AUDIO_MESSAGE_PLAYBACK_VOLUME ?
currentPlaybackNL(audio, message->streamGeneration) :
currentCaptureNL(audio, message->streamGeneration);
const uint8_t channels =
message->type == KVMFR_AUDIO_MESSAGE_PLAYBACK_VOLUME ?
audio->playback.format.channelCount :
audio->capture.format.channelCount;
LG_UNLOCK(audio->lock);
if (!current)
return true;
if (!validVolume(&volume, channels))
return false;
if (message->type == KVMFR_AUDIO_MESSAGE_PLAYBACK_VOLUME)
dispatchPlaybackVolume(
audio, message->streamGeneration, &volume);
else
dispatchCaptureVolume(
audio, message->streamGeneration, &volume);
return true;
}
case KVMFR_AUDIO_MESSAGE_PLAYBACK_MUTE:
case KVMFR_AUDIO_MESSAGE_CAPTURE_MUTE:
if (!message->streamGeneration ||
!validEmptyMessage(message, KVMFR_AUDIO_MESSAGE_MUTED))
return false;
if (message->type == KVMFR_AUDIO_MESSAGE_PLAYBACK_MUTE)
dispatchPlaybackMute(audio, message->streamGeneration,
(message->flags & KVMFR_AUDIO_MESSAGE_MUTED) != 0);
else
dispatchCaptureMute(audio, message->streamGeneration,
(message->flags & KVMFR_AUDIO_MESSAGE_MUTED) != 0);
return true;
case KVMFR_AUDIO_MESSAGE_PLAYBACK_DATA:
if (!message->streamGeneration || !message->frames ||
message->frames > KVMFR_AUDIO_MAX_FRAMES_PER_PACKET ||
(message->flags & ~AUDIO_DATA_FLAGS) ||
!validClockFlags(message->flags & AUDIO_CLOCK_FLAGS) ||
(!(message->flags & KVMFR_AUDIO_MESSAGE_CLOCK_VALID) &&
(message->position || message->timeNs || message->rateQ32)))
return false;
dispatchPlaybackData(audio, message, payload, sequenceGap);
return true;
default:
return false;
}
}
enum WriteResult
{
WRITE_OK,
WRITE_FULL,
WRITE_LOST,
};
static enum WriteResult writeMessageLocked(LGMPAudio * audio,
KVMFRAudioMessageType type, uint64_t streamGeneration,
KVMFRAudioMessageFlags flags, uint64_t position, int64_t timeNs,
uint64_t rateQ32, uint32_t frames, const void * payload,
uint32_t length)
{
if (!audio->writeBound || !audio->clientToHostStream)
return WRITE_LOST;
LGMPStreamBuffer buffer = { 0 };
LGMP_STATUS status = lgmpClientStreamWriteAcquire(
audio->clientToHostStream, &buffer);
if (status == LGMP_ERR_STREAM_FULL)
return WRITE_FULL;
if (status != LGMP_OK)
{
atomic_store_explicit(
&audio->dataPlaneLost, true, memory_order_release);
return WRITE_LOST;
}
const uint32_t bytes = (uint32_t)sizeof(KVMFRAudioMessage) + length;
if (!buffer.data || buffer.capacity < bytes)
{
(void)lgmpClientStreamWriteCancel(
audio->clientToHostStream, &buffer);
atomic_store_explicit(
&audio->dataPlaneLost, true, memory_order_release);
return WRITE_LOST;
}
const KVMFRAudioMessage message =
{
.version = KVMFR_AUDIO_MESSAGE_VERSION,
.size = sizeof(KVMFRAudioMessage),
.type = type,
.streamGeneration = streamGeneration,
.flags = flags,
.sequence = audio->clientSequence,
.position = position,
.timeNs = timeNs,
.rateQ32 = rateQ32,
.frames = frames,
.length = length,
};
memcpy(buffer.data, &message, sizeof(message));
if (length)
memcpy((uint8_t *)buffer.data + sizeof(message), payload, length);
status = lgmpClientStreamWriteCommit(
audio->clientToHostStream, &buffer, bytes);
if (status == LGMP_OK)
{
++audio->clientSequence;
return WRITE_OK;
}
atomic_store_explicit(
&audio->dataPlaneLost, true, memory_order_release);
return WRITE_LOST;
}
static bool validResponse(const LGMPAudio * audio,
const KVMFRAudioStreamResponse * response)
{
if (response->magic != KVMFR_AUDIO_RESPONSE_MAGIC ||
response->version != KVMFR_AUDIO_CONTROL_VERSION ||
response->size != sizeof(*response) ||
response->requestGeneration != audio->requestGeneration ||
response->boundClientID != audio->clientID ||
response->claimToken != audio->claimToken ||
response->result < KVMFR_AUDIO_RESPONSE_BOUND ||
response->result > KVMFR_AUDIO_RESPONSE_ERROR)
return false;
for (unsigned i = 0; i < sizeof(response->cursorCompatibility); ++i)
if (response->cursorCompatibility[i])
return false;
if (response->result == KVMFR_AUDIO_RESPONSE_BOUND)
return response->protocolVersion == KVMFR_AUDIO_PROTOCOL_MAX &&
response->endpointGeneration &&
validDescriptor(&response->hostToClient,
LGMP_STREAM_HOST_TO_CLIENT) &&
validDescriptor(&response->clientToHost,
LGMP_STREAM_CLIENT_TO_HOST);
const KVMFRStreamDescriptor empty = { 0 };
return !response->protocolVersion &&
!memcmp(&response->hostToClient, &empty, sizeof(empty)) &&
!memcmp(&response->clientToHost, &empty, sizeof(empty));
}
static void processResponse(LGMPAudio * audio)
{
KVMFRAudioStreamResponse response;
bool pending = false;
LG_LOCK(audio->lock);
if (audio->responsePending)
{
response = audio->response;
audio->responsePending = false;
pending = true;
}
LG_UNLOCK(audio->lock);
if (!pending)
return;
LG_LOCK(audio->lock);
const bool current = audio->connected &&
(audio->phase == AUDIO_PHASE_CLAIM_SENT ||
audio->phase == AUDIO_PHASE_CLAIM_PENDING ||
audio->phase == AUDIO_PHASE_COOLDOWN) &&
response.requestGeneration == audio->requestGeneration &&
response.claimToken == audio->claimToken;
const bool valid = current && validResponse(audio, &response);
LG_UNLOCK(audio->lock);
if (!current)
return;
if (!valid)
{
DEBUG_WARN("Ignoring malformed matching LGMP audio response");
LG_LOCK(audio->lock);
audio->phase = AUDIO_PHASE_COOLDOWN;
audio->releaseDeadline =
microtime() + AUDIO_OWNER_RETRY_US;
LG_UNLOCK(audio->lock);
return;
}
if (response.result != KVMFR_AUDIO_RESPONSE_BOUND)
{
if (response.result == KVMFR_AUDIO_RESPONSE_INCOMPATIBLE)
DEBUG_WARN("The host LGMP audio protocol is incompatible");
LG_LOCK(audio->lock);
scheduleRetryNL(audio, response.result ==
KVMFR_AUDIO_RESPONSE_INCOMPATIBLE ?
AUDIO_OWNER_RETRY_US : AUDIO_CLAIM_RETRY_US);
LG_UNLOCK(audio->lock);
if (audio->pointerWake)
lgSignalEvent(audio->pointerWake);
return;
}
if (!attachStreams(audio, &response))
{
LG_LOCK(audio->lock);
audio->phase = AUDIO_PHASE_RELEASING;
audio->releaseRequested = true;
audio->releaseSent = false;
audio->releaseDeadline =
microtime() + AUDIO_OWNER_RETRY_US;
audio->nextAttachAt = microtime() + AUDIO_CLAIM_RETRY_US;
LG_UNLOCK(audio->lock);
return;
}
LG_LOCK(audio->lock);
if (!audio->connected ||
response.requestGeneration != audio->requestGeneration ||
response.claimToken != audio->claimToken)
{
audio->releaseRequested = true;
audio->phase = AUDIO_PHASE_RELEASING;
}
else
{
mapGeneration(audio, &audio->endpointGeneration,
response.endpointGeneration);
audio->phase = audio->releaseRequested || !audio->discover ?
AUDIO_PHASE_RELEASING : AUDIO_PHASE_BOUND;
}
audio->sequenceKnown = false;
audio->expectedSequence = 0;
audio->releaseSent = false;
audio->nextKeepalive = microtime();
LG_UNLOCK(audio->lock);
DEBUG_INFO("LGMP audio streams attached for request %u",
response.requestGeneration);
}
static unsigned drainInbound(LGMPAudio * audio)
{
unsigned drained = 0;
while (audio->hostToClientStream && audio->hostReadActive &&
drained < AUDIO_DRAIN_MAX)
{
LGMPStreamBuffer buffer = { 0 };
LGMP_STATUS status = lgmpClientStreamReadPeek(
audio->hostToClientStream, &buffer);
if (status == LGMP_ERR_STREAM_EMPTY)
break;
if (status == LGMP_ERR_STREAM_UNBOUND ||
status == LGMP_ERR_STREAM_STALE)
{
atomic_store_explicit(
&audio->dataPlaneLost, true, memory_order_release);
break;
}
if (status != LGMP_OK)
{
DEBUG_WARN("Failed to read LGMP audio stream: %s",
lgmpStatusString(status));
atomic_store_explicit(
&audio->dataPlaneLost, true, memory_order_release);
break;
}
++drained;
LG_LOCK(audio->lock);
const bool releasing = audio->phase == AUDIO_PHASE_RELEASING;
LG_UNLOCK(audio->lock);
bool valid = true;
if (!releasing)
{
KVMFRAudioMessage message;
const uint8_t * payload;
valid = parseMessage(&buffer, &message, &payload);
bool sequenceGap = false;
if (valid)
{
LG_LOCK(audio->lock);
sequenceGap = audio->sequenceKnown ?
message.sequence != audio->expectedSequence :
message.sequence != 0;
audio->sequenceKnown = true;
audio->expectedSequence = message.sequence + 1;
LG_UNLOCK(audio->lock);
valid = processInboundMessage(
audio, &message, payload, sequenceGap);
}
}
status = lgmpClientStreamReadRelease(
audio->hostToClientStream, &buffer);
if (status != LGMP_OK)
{
if (status != LGMP_ERR_STREAM_UNBOUND &&
status != LGMP_ERR_STREAM_STALE)
DEBUG_WARN("Failed to release LGMP audio record: %s",
lgmpStatusString(status));
atomic_store_explicit(
&audio->dataPlaneLost, true, memory_order_release);
break;
}
if (!valid)
{
requestRelease(audio, "malformed host-to-client record");
break;
}
}
return drained;
}
static bool bindingGone(LGMPAudio * audio)
{
bool any = false;
const PLGMPClientStream streams[2] =
{
audio->hostToClientStream,
audio->clientToHostStream,
};
for (unsigned i = 0; i < 2; ++i)
{
if (!streams[i])
continue;
any = true;
uint32_t clientID = 0;
uint32_t epoch = 0;
if (i == 1)
lockWriteGate(audio);
const LGMP_STATUS status = lgmpClientStreamGetBinding(
streams[i], &clientID, &epoch);
if (i == 1)
unlockWriteGate(audio);
if (status == LGMP_OK && clientID == audio->clientID && epoch)
return false;
if (status != LGMP_OK && status != LGMP_ERR_STREAM_UNBOUND &&
status != LGMP_ERR_STREAM_STALE)
{
DEBUG_WARN("Failed to query LGMP audio binding: %s",
lgmpStatusString(status));
return false;
}
}
return any;
}
static void maintainBinding(LGMPAudio * audio)
{
const uint64_t now = microtime();
LG_LOCK(audio->lock);
const enum AudioPhase phase = audio->phase;
const bool releaseRequested = audio->releaseRequested;
const bool releaseSent = audio->releaseSent;
const uint64_t deadline = audio->releaseDeadline;
const uint64_t nextAttachAt = audio->nextAttachAt;
const bool keepalive = phase == AUDIO_PHASE_BOUND &&
!releaseRequested && now >= audio->nextKeepalive;
LG_UNLOCK(audio->lock);
if ((phase == AUDIO_PHASE_CLAIM_PENDING ||
phase == AUDIO_PHASE_CLAIM_SENT ||
phase == AUDIO_PHASE_COOLDOWN) &&
releaseRequested && now >= deadline)
{
LG_LOCK(audio->lock);
if ((audio->phase == AUDIO_PHASE_CLAIM_PENDING ||
audio->phase == AUDIO_PHASE_CLAIM_SENT ||
audio->phase == AUDIO_PHASE_COOLDOWN) &&
audio->releaseRequested &&
!audio->claimPosting)
{
audio->responsePending = false;
scheduleRetryNL(audio, AUDIO_CLAIM_RETRY_US);
}
LG_UNLOCK(audio->lock);
return;
}
if (phase == AUDIO_PHASE_COOLDOWN && now >= deadline)
{
bool wakePointer = false;
LG_LOCK(audio->lock);
if (audio->phase == AUDIO_PHASE_COOLDOWN &&
now >= audio->releaseDeadline &&
!audio->releaseRequested && audio->discover)
{
audio->phase = audio->claimSends ?
AUDIO_PHASE_CLAIM_SENT : AUDIO_PHASE_CLAIM_PENDING;
audio->claimSends = 0;
audio->nextClaimAt = 0;
wakePointer = true;
}
else if (audio->phase == AUDIO_PHASE_COOLDOWN &&
!audio->claimSends && !audio->claimPosting)
scheduleRetryNL(audio, AUDIO_CLAIM_RETRY_US);
LG_UNLOCK(audio->lock);
if (wakePointer && audio->pointerWake)
lgSignalEvent(audio->pointerWake);
return;
}
if (phase != AUDIO_PHASE_BOUND &&
phase != AUDIO_PHASE_RELEASING)
return;
if (phase == AUDIO_PHASE_RELEASING &&
(!audio->hostReadActive || !audio->clientWriteActive) &&
now >= nextAttachAt)
{
LG_LOCK(audio->lock);
const KVMFRAudioStreamResponse response = audio->response;
audio->nextAttachAt = now + AUDIO_CLAIM_RETRY_US;
LG_UNLOCK(audio->lock);
attachStreams(audio, &response);
}
if (phase == AUDIO_PHASE_RELEASING && !releaseSent &&
audio->clientWriteActive)
{
lockWriteGate(audio);
const enum WriteResult result = writeMessageLocked(audio,
KVMFR_AUDIO_MESSAGE_RELEASE, 0, 0, 0, 0, 0, 0, NULL, 0);
unlockWriteGate(audio);
if (result == WRITE_OK)
{
LG_LOCK(audio->lock);
if (audio->phase == AUDIO_PHASE_RELEASING)
audio->releaseSent = true;
LG_UNLOCK(audio->lock);
}
}
else if (keepalive)
{
lockWriteGate(audio);
const enum WriteResult result = writeMessageLocked(audio,
KVMFR_AUDIO_MESSAGE_KEEPALIVE, 0, 0, 0, 0, 0, 0, NULL, 0);
unlockWriteGate(audio);
if (result == WRITE_OK)
{
LG_LOCK(audio->lock);
if (audio->phase == AUDIO_PHASE_BOUND)
audio->nextKeepalive =
now + (uint64_t)KVMFR_AUDIO_KEEPALIVE_MS * 1000U;
LG_UNLOCK(audio->lock);
}
}
if (phase == AUDIO_PHASE_RELEASING && bindingGone(audio))
finishBinding(audio, AUDIO_CLAIM_RETRY_US);
}
static void stopWorkerBinding(LGMPAudio * audio)
{
LG_LOCK(audio->lock);
const bool active = audio->phase == AUDIO_PHASE_BOUND ||
audio->phase == AUDIO_PHASE_RELEASING;
audio->phase = active ? AUDIO_PHASE_RELEASING : audio->phase;
LG_UNLOCK(audio->lock);
if (!active)
{
stopActiveStreams(audio);
detachStreams(audio);
return;
}
for (;;)
{
drainInbound(audio);
maintainBinding(audio);
if (bindingGone(audio) || !lgmpClientSessionValid(audio->client))
break;
lgWaitEvent(audio->event, 1);
}
stopActiveStreams(audio);
detachStreams(audio);
}
static int audioThread(void * opaque)
{
LGMPAudio * audio = opaque;
const struct LGMPStreamPollConfig pollConfig =
{
.spinCount = 64,
.minWaitUs = 25,
.maxWaitUs = AUDIO_ACTIVE_POLL_US,
};
if (lgmpStreamPollInit(&audio->streamPoll, pollConfig) != LGMP_OK)
return -1;
while (!atomic_load_explicit(&audio->stop, memory_order_acquire))
{
processResponse(audio);
const unsigned drained = drainInbound(audio);
if (drained)
lgmpStreamPollActivity(&audio->streamPoll);
maintainBinding(audio);
if (atomic_exchange_explicit(
&audio->dataPlaneLost, false, memory_order_acq_rel))
{
requestRelease(audio, "audio stream operation failed");
if (bindingGone(audio))
finishBinding(audio, AUDIO_CLAIM_RETRY_US);
}
publishStatus(audio);
if (atomic_load_explicit(&audio->stop, memory_order_acquire))
break;
LG_LOCK(audio->lock);
const bool active = audio->phase == AUDIO_PHASE_BOUND ||
audio->phase == AUDIO_PHASE_RELEASING;
LG_UNLOCK(audio->lock);
if (active)
{
if (drained == AUDIO_DRAIN_MAX)
{
lgmpStreamPollActivity(&audio->streamPoll);
continue;
}
const uint32_t waitUs = lgmpStreamPollIdle(&audio->streamPoll);
if (!waitUs)
continue;
if (lgWaitEventNS(audio->event, waitUs * 1000U))
lgmpStreamPollActivity(&audio->streamPoll);
}
else
lgWaitEvent(audio->event, AUDIO_IDLE_POLL_MS);
}
stopWorkerBinding(audio);
publishStatus(audio);
return 0;
}
bool lgmpAudio_create(PLGMPClient client, LGEvent * pointerWake,
LGMPAudio ** result)
{
if (!client || !pointerWake || !result)
return false;
LGMPAudio * audio = calloc(1, sizeof(*audio));
if (!audio)
return false;
audio->client = client;
audio->pointerWake = pointerWake;
audio->event = lgCreateEvent(true, 0);
if (!audio->event)
{
free(audio);
return false;
}
audio->generationSerial = 1;
if (!randomBytes(&audio->requestSerial,
sizeof(audio->requestSerial)))
{
lgFreeEvent(audio->event);
free(audio);
return false;
}
if (!audio->requestSerial)
audio->requestSerial = 1;
LG_LOCK_INIT(audio->lock);
LG_LOCK_INIT(audio->eventLock);
LG_LOCK_INIT(audio->statusWait.lock);
atomic_flag_clear(&audio->writeGate);
atomic_init(&audio->stop, false);
atomic_init(&audio->dataPlaneLost, false);
atomic_init(&audio->captureDiscontinuity, false);
atomic_init(&audio->clockFeedbackAvailable, false);
atomic_init(&audio->statusInFlight, 0);
atomic_init(&audio->statusWait.count, 0);
*result = audio;
return true;
}
void lgmpAudio_destroy(LGMPAudio ** audio)
{
if (!audio || !*audio)
return;
lgmpAudio_disconnect(*audio);
lgFreeEvent((*audio)->event);
LG_LOCK_FREE((*audio)->statusWait.lock);
LG_LOCK_FREE((*audio)->eventLock);
LG_LOCK_FREE((*audio)->lock);
free(*audio);
*audio = NULL;
}
bool lgmpAudio_connect(LGMPAudio * audio, uint32_t clientID)
{
if (!audio || !clientID)
return false;
LG_LOCK(audio->lock);
if (audio->connected)
{
const bool same = audio->clientID == clientID;
LG_UNLOCK(audio->lock);
return same;
}
if (audio->thread)
{
LG_UNLOCK(audio->lock);
return false;
}
lgResetEvent(audio->event);
audio->connected = true;
audio->clientID = clientID;
audio->phase = AUDIO_PHASE_IDLE;
audio->statusAvailable = false;
audio->statusDirty = true;
audio->nextClaimAt = 0;
audio->releaseRequested = false;
audio->releaseSent = false;
memset(&audio->endpointGeneration, 0,
sizeof(audio->endpointGeneration));
atomic_store_explicit(&audio->stop, false, memory_order_release);
atomic_store_explicit(
&audio->dataPlaneLost, false, memory_order_release);
LGThread * thread;
if (lgCreateThread("lgmpAudio", audioThread, audio, &thread))
{
audio->thread = thread;
LG_UNLOCK(audio->lock);
return true;
}
audio->connected = false;
LG_UNLOCK(audio->lock);
return false;
}
void lgmpAudio_disconnect(LGMPAudio * audio)
{
if (!audio)
return;
LG_LOCK(audio->eventLock);
lockWriteGate(audio);
LG_LOCK(audio->lock);
closePublicDataLocked(audio);
audio->events = NULL;
audio->eventOpaque = NULL;
audio->attached = false;
audio->discover = false;
audio->releaseRequested = true;
audio->releaseDeadline =
microtime() + AUDIO_OWNER_RETRY_US;
if (audio->phase == AUDIO_PHASE_BOUND)
audio->phase = AUDIO_PHASE_RELEASING;
markStatusNL(audio, false, 0);
audio->connected = false;
LGThread * thread = audio->thread;
LGEvent * event = audio->event;
atomic_store_explicit(&audio->stop, true, memory_order_release);
LG_UNLOCK(audio->lock);
unlockWriteGate(audio);
LG_UNLOCK(audio->eventLock);
if (event)
lgSignalEvent(event);
if (thread)
lgJoinThread(thread, NULL);
LG_LOCK(audio->lock);
audio->thread = NULL;
audio->clientID = 0;
audio->phase = AUDIO_PHASE_IDLE;
audio->responsePending = false;
audio->claimPosting = false;
audio->requestGeneration = 0;
audio->claimToken = 0;
audio->claimSends = 0;
audio->releaseRequested = false;
audio->releaseSent = false;
audio->statusCallback = NULL;
audio->statusOpaque = NULL;
memset(&audio->claim, 0, sizeof(audio->claim));
memset(&audio->response, 0, sizeof(audio->response));
memset(&audio->remoteStatus, 0, sizeof(audio->remoteStatus));
LG_UNLOCK(audio->lock);
waitStatusCallbacks(audio);
}
static bool newClaimNL(LGMPAudio * audio)
{
uint64_t token = 0;
if (!randomBytes(&token, sizeof(token)))
{
token = microtime() ^ ((uint64_t)audio->clientID << 32) ^
audio->requestSerial;
}
if (!token)
token = 1;
audio->requestGeneration = nextNonzero(&audio->requestSerial);
audio->claimToken = token;
audio->claim = (KVMFRAudioClaim)
{
.messageType = KVMFR_MESSAGE_AUDIO_CLAIM,
.version = KVMFR_AUDIO_CONTROL_VERSION,
.size = sizeof(KVMFRAudioClaim),
.requestGeneration = audio->requestGeneration,
.flags = KVMFR_AUDIO_CLAIM_BIND,
.claimToken = audio->claimToken,
.protocolMin = KVMFR_AUDIO_PROTOCOL_MIN,
.protocolMax = KVMFR_AUDIO_PROTOCOL_MAX,
.clientCapabilities = AUDIO_CAPABILITIES,
};
audio->claimSends = 0;
audio->claimPosting = false;
audio->phase = AUDIO_PHASE_CLAIM_PENDING;
audio->nextClaimAt = 0;
return true;
}
void lgmpAudio_pumpClaim(LGMPAudio * audio,
PLGMPClientQueue pointerQueue)
{
if (!audio || !pointerQueue)
return;
KVMFRAudioClaim claim;
bool send = false;
const uint64_t now = microtime();
LG_LOCK(audio->lock);
if (audio->connected && audio->discover &&
audio->phase == AUDIO_PHASE_IDLE &&
now >= audio->nextClaimAt)
newClaimNL(audio);
if (audio->connected && !audio->releaseRequested &&
(audio->phase == AUDIO_PHASE_CLAIM_PENDING ||
audio->phase == AUDIO_PHASE_CLAIM_SENT) &&
now >= audio->nextClaimAt)
{
if (audio->claimSends >= 3)
{
audio->phase = AUDIO_PHASE_COOLDOWN;
audio->releaseDeadline = now + AUDIO_OWNER_RETRY_US;
}
else
{
claim = audio->claim;
send = true;
audio->claimPosting = true;
}
}
LG_UNLOCK(audio->lock);
if (!send)
return;
uint32_t serial = 0;
const LGMP_STATUS status = lgmpClientSendData(pointerQueue,
&claim, sizeof(claim), &serial);
LG_LOCK(audio->lock);
if (claim.requestGeneration == audio->requestGeneration &&
claim.claimToken == audio->claimToken)
{
audio->claimPosting = false;
if (audio->connected)
{
if (status == LGMP_OK)
{
audio->phase = AUDIO_PHASE_CLAIM_SENT;
++audio->claimSends;
audio->nextClaimAt = now + AUDIO_CLAIM_RETRY_US;
}
else if (status == LGMP_ERR_QUEUE_FULL ||
status == LGMP_ERR_QUEUE_BUSY)
audio->nextClaimAt = now + 1000U;
else
{
audio->nextClaimAt = now + AUDIO_CLAIM_RETRY_US;
DEBUG_WARN("Failed to send LGMP audio claim: %s",
lgmpStatusString(status));
}
}
}
LG_UNLOCK(audio->lock);
}
void lgmpAudio_handleResponse(LGMPAudio * audio,
const KVMFRAudioStreamResponse * response)
{
if (!audio || !response)
return;
LG_LOCK(audio->lock);
if (audio->connected &&
response->requestGeneration == audio->requestGeneration &&
response->claimToken == audio->claimToken &&
response->boundClientID == audio->clientID &&
(audio->phase == AUDIO_PHASE_CLAIM_PENDING ||
audio->phase == AUDIO_PHASE_CLAIM_SENT ||
audio->phase == AUDIO_PHASE_COOLDOWN) &&
!audio->responsePending)
{
audio->response = *response;
audio->responsePending = true;
}
LG_UNLOCK(audio->lock);
signalWorker(audio);
}
static void setStatusListener(void * opaque,
LG_AudioStatusFn callback, void * callbackOpaque)
{
LGMPAudio * audio = opaque;
if (!callback)
lockWriteGate(audio);
LG_LOCK(audio->lock);
if (!callback)
closePublicDataLocked(audio);
audio->statusCallback = callback;
audio->statusOpaque = callbackOpaque;
audio->discover = callback && audio->connected;
if (audio->discover && audio->phase == AUDIO_PHASE_IDLE)
{
audio->releaseRequested = false;
audio->nextClaimAt = 0;
}
if (!callback)
{
const bool ownsOrMayOwn =
audio->phase != AUDIO_PHASE_IDLE;
audio->releaseRequested = ownsOrMayOwn;
if (ownsOrMayOwn)
{
audio->releaseDeadline =
microtime() + AUDIO_OWNER_RETRY_US;
if (audio->phase == AUDIO_PHASE_BOUND)
audio->phase = AUDIO_PHASE_RELEASING;
}
markStatusNL(audio, false, 0);
}
const LG_AudioStatus status =
{
.available = audio->connected && audio->statusAvailable,
.generation = audio->endpointGeneration.local,
};
struct AudioStatusCallbackFrame frame;
if (callback)
beginStatusCallback(audio, &frame);
audio->statusDirty = false;
LG_UNLOCK(audio->lock);
if (!callback)
unlockWriteGate(audio);
if (callback)
{
callback(callbackOpaque, &status);
endStatusCallback(audio, &frame);
}
else
waitStatusCallbacks(audio);
if (callback && audio->pointerWake)
lgSignalEvent(audio->pointerWake);
signalWorker(audio);
}
static bool attach(void * opaque, const LG_AudioEventOps * events,
void * eventOpaque)
{
LGMPAudio * audio = opaque;
if (!events)
return false;
struct AudioStreamState playback;
struct AudioStreamState capture;
bool admitted;
LG_LOCK(audio->eventLock);
LG_LOCK(audio->lock);
admitted = audio->connected && audio->statusAvailable &&
audio->phase == AUDIO_PHASE_BOUND && !audio->releaseRequested &&
!audio->attached;
if (admitted)
{
audio->events = events;
audio->eventOpaque = eventOpaque;
audio->attached = true;
}
playback = audio->playback;
capture = audio->capture;
LG_UNLOCK(audio->lock);
if (admitted && playback.active && events->playbackStart)
{
events->playbackStart(eventOpaque, playback.generation.local,
&playback.format,
playback.clockValid ? &playback.clock : NULL);
if (playback.volumeValid && events->playbackVolume)
events->playbackVolume(eventOpaque, playback.generation.local,
playback.volumeChannels, playback.volume);
if (playback.muteValid && events->playbackMute)
events->playbackMute(eventOpaque, playback.generation.local,
playback.mute);
}
if (admitted && capture.active && events->recordStart)
{
events->recordStart(eventOpaque, capture.generation.local,
&capture.format);
if (capture.volumeValid && events->recordVolume)
events->recordVolume(eventOpaque, capture.generation.local,
capture.volumeChannels, capture.volume);
if (capture.muteValid && events->recordMute)
events->recordMute(eventOpaque, capture.generation.local,
capture.mute);
}
LG_UNLOCK(audio->eventLock);
if (admitted)
signalWorker(audio);
return admitted;
}
static void detach(void * opaque)
{
LGMPAudio * audio = opaque;
LG_LOCK(audio->eventLock);
LG_LOCK(audio->lock);
audio->attached = false;
audio->events = NULL;
audio->eventOpaque = NULL;
LG_UNLOCK(audio->lock);
LG_UNLOCK(audio->eventLock);
}
static bool rateToQ32(double rate, bool allowZero, uint64_t * result)
{
if (!isfinite(rate) || rate < 0.0 || (!allowZero && rate == 0.0) ||
rate > (double)KVMFR_AUDIO_MAX_SAMPLE_RATE * 2.0)
return false;
const long double scaled = (long double)rate * 4294967296.0L;
if (scaled > (long double)UINT64_MAX)
return false;
*result = (uint64_t)(scaled + 0.5L);
return allowZero || *result != 0;
}
static bool recordData(void * opaque, uint32_t generation,
const void * data, size_t frames, const LG_AudioClock * sourceClock)
{
LGMPAudio * audio = opaque;
if (!generation || !data || !frames ||
frames > KVMFR_AUDIO_MAX_FRAMES_PER_PACKET)
return false;
if (!tryWriteGate(audio))
{
atomic_store_explicit(
&audio->captureDiscontinuity, true, memory_order_release);
return false;
}
if (!audio->publicDataAllowed || !audio->writeBound ||
generation != audio->writeCaptureLocalGeneration ||
!audio->writeCaptureGeneration)
{
unlockWriteGate(audio);
return false;
}
const size_t bytes = frames * audio->writeCaptureFormat.blockAlign;
if (!audio->writeCaptureFormat.blockAlign ||
bytes > KVMFR_AUDIO_PCM_BYTES)
{
unlockWriteGate(audio);
return false;
}
uint32_t flags = 0;
uint64_t position = 0;
int64_t timeNs = 0;
uint64_t rateQ32 = 0;
if (sourceClock)
{
if (!rateToQ32(sourceClock->rate, true, &rateQ32))
{
unlockWriteGate(audio);
return false;
}
flags |= KVMFR_AUDIO_MESSAGE_CLOCK_VALID;
position = sourceClock->position;
timeNs = sourceClock->time;
if (sourceClock->stable)
flags |= KVMFR_AUDIO_MESSAGE_CLOCK_STABLE;
if (sourceClock->discontinuity)
flags |= KVMFR_AUDIO_MESSAGE_DISCONTINUITY;
}
if (atomic_exchange_explicit(
&audio->captureDiscontinuity, false, memory_order_acq_rel))
flags |= KVMFR_AUDIO_MESSAGE_DISCONTINUITY;
const enum WriteResult result = writeMessageLocked(audio,
KVMFR_AUDIO_MESSAGE_CAPTURE_DATA,
audio->writeCaptureGeneration, flags, position, timeNs, rateQ32,
(uint32_t)frames, data, (uint32_t)bytes);
if (result != WRITE_OK)
{
if (result == WRITE_FULL)
++audio->clientSequence;
atomic_store_explicit(
&audio->captureDiscontinuity, true, memory_order_release);
}
unlockWriteGate(audio);
return result == WRITE_OK;
}
static bool clockFeedback(void * opaque, uint32_t generation,
const LG_AudioClock * playbackClock, double targetRate)
{
LGMPAudio * audio = opaque;
uint64_t targetRateQ32;
uint64_t rateQ32;
if (!generation || !playbackClock ||
!rateToQ32(targetRate, false, &targetRateQ32) ||
!rateToQ32(playbackClock->rate, true, &rateQ32))
return false;
if (!tryWriteGate(audio))
{
atomic_store_explicit(
&audio->captureDiscontinuity, true, memory_order_release);
return false;
}
LG_LOCK(audio->lock);
const bool supported = audio->phase == AUDIO_PHASE_BOUND &&
!audio->releaseRequested && audio->statusAvailable &&
(audio->remoteStatus.capabilities &
KVMFR_AUDIO_CAP_CLOCK_FEEDBACK) != 0 &&
audio->playback.active &&
audio->playback.generation.local == generation;
LG_UNLOCK(audio->lock);
if (!supported)
{
unlockWriteGate(audio);
return false;
}
if (!audio->publicDataAllowed || !audio->writeBound ||
generation != audio->writePlaybackLocalGeneration ||
!audio->writePlaybackGeneration)
{
unlockWriteGate(audio);
return false;
}
uint32_t flags = KVMFR_AUDIO_MESSAGE_CLOCK_VALID;
if (playbackClock->stable)
flags |= KVMFR_AUDIO_MESSAGE_CLOCK_STABLE;
if (playbackClock->discontinuity)
flags |= KVMFR_AUDIO_MESSAGE_DISCONTINUITY;
const KVMFRAudioClockFeedback feedback =
{
.targetRateQ32 = targetRateQ32,
};
const enum WriteResult result = writeMessageLocked(audio,
KVMFR_AUDIO_MESSAGE_CLOCK_FEEDBACK,
audio->writePlaybackGeneration, flags,
playbackClock->position, playbackClock->time, rateQ32, 0,
&feedback, sizeof(feedback));
if (result != WRITE_OK)
{
if (result == WRITE_FULL)
++audio->clientSequence;
atomic_store_explicit(
&audio->captureDiscontinuity, true, memory_order_release);
}
unlockWriteGate(audio);
return result == WRITE_OK;
}
static bool clockFeedbackAvailable(void * opaque)
{
LGMPAudio * audio = opaque;
return atomic_load_explicit(
&audio->clockFeedbackAvailable, memory_order_acquire);
}
static const LG_AudioOps l_audioOps =
{
.name = "LGMP",
.playbackArrivalReserveUs = AUDIO_ACTIVE_POLL_US,
.setStatusListener = setStatusListener,
.attach = attach,
.detach = detach,
.recordData = recordData,
.clockFeedback = clockFeedback,
.clockFeedbackAvailable = clockFeedbackAvailable,
};
const LG_AudioOps * lgmpAudio_getOps(void)
{
return &l_audioOps;
}