/** * 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 #include "transport/lgmp/CLGMPAudioTransport.h" #include #include "CDebug.h" #include "WCWrite.h" #include "transport/lgmp/CLGMPControl.h" #include "transport/lgmp/CLGMPHost.h" #include #include #include #include #ifndef CREATE_WAITABLE_TIMER_HIGH_RESOLUTION #define CREATE_WAITABLE_TIMER_HIGH_RESOLUTION 0x00000002 #endif namespace { static_assert(AUDIO_MAX_CHANNELS == KVMFR_AUDIO_MAX_CHANNELS, "audio channel limits differ"); static_assert(AUDIO_STREAM_MAX_CHANNELS == KVMFR_AUDIO_STREAM_MAX_CHANNELS, "audio stream channel limits differ"); static_assert(AUDIO_MAX_FRAMES_PER_PACKET == KVMFR_AUDIO_MAX_FRAMES_PER_PACKET, "audio packet limits differ"); static_assert(AUDIO_MAX_PCM_BYTES == KVMFR_AUDIO_PCM_BYTES, "audio PCM limits differ"); static_assert(static_cast(AUDIO_CAP_PLAYBACK) == static_cast(KVMFR_AUDIO_CAP_PLAYBACK) && static_cast(AUDIO_CAP_CAPTURE) == static_cast(KVMFR_AUDIO_CAP_CAPTURE) && static_cast(AUDIO_CAP_CLOCK_FEEDBACK) == static_cast(KVMFR_AUDIO_CAP_CLOCK_FEEDBACK), "audio capability bits differ"); static_assert(sizeof(LGMPStreamDescriptor) == sizeof(KVMFRStreamDescriptor), "LGMP and KVMFR stream descriptor sizes differ"); constexpr uint32_t AUDIO_CAPABILITY_MASK = KVMFR_AUDIO_CAP_PLAYBACK | KVMFR_AUDIO_CAP_CAPTURE | KVMFR_AUDIO_CAP_CLOCK_FEEDBACK; constexpr uint32_t AUDIO_DIRECTION_MASK = KVMFR_AUDIO_CAP_PLAYBACK | KVMFR_AUDIO_CAP_CAPTURE; constexpr uint32_t AUDIO_PACKET_FLAG_MASK = AUDIO_PACKET_DISCONTINUITY | AUDIO_PACKET_SILENT | AUDIO_PACKET_CLOCK_VALID | AUDIO_PACKET_CLOCK_STABLE; constexpr uint32_t WIRE_PACKET_FLAG_MASK = KVMFR_AUDIO_MESSAGE_DISCONTINUITY | KVMFR_AUDIO_MESSAGE_SILENT | KVMFR_AUDIO_MESSAGE_CLOCK_VALID | KVMFR_AUDIO_MESSAGE_CLOCK_STABLE; constexpr size_t AUDIO_SHARED_MEMORY_PREFLIGHT = 2U * 1024U * 1024U; bool ArmAudioPollTimer(HANDLE timer, uint32_t waitUs) { LARGE_INTEGER due = {}; due.QuadPart = -static_cast(waitUs) * 10; return SetWaitableTimer(timer, &due, 0, nullptr, nullptr, FALSE) != FALSE; } uint32_t ChannelCount(uint32_t mask) { uint32_t count = 0; while (mask) { count += mask & 1U; mask >>= 1; } return count; } uint64_t NextSerial(uint64_t serial) { ++serial; return serial ? serial : 1; } bool RequestNewer(uint32_t candidate, uint32_t previous) { return candidate != previous && static_cast(candidate - previous) > 0; } } CLGMPAudioTransport::~CLGMPAudioTransport() { TransportStop(); CSRWExclusiveLock lock(m_lock); for (ResponseMemory& response : m_responseMemory) lgmpHostMemFree(&response.memory); FreeStreams(); m_initialized = false; } bool CLGMPAudioTransport::SameFormat( const AudioFormat& left, const AudioFormat& right) { return left.sampleRate == right.sampleRate && left.channelMask == right.channelMask && left.channels == right.channels && left.containerBits == right.containerBits && left.validBits == right.validBits && left.blockAlign == right.blockAlign; } bool CLGMPAudioTransport::SamePacket( const AudioPacket& left, const AudioPacket& right) { return left.endpointGeneration == right.endpointGeneration && left.streamGeneration == right.streamGeneration && left.flags == right.flags && left.frames == right.frames && left.position == right.position && left.timeNs == right.timeNs && left.rateQ32 == right.rateQ32 && SameFormat(left.format, right.format); } bool CLGMPAudioTransport::ValidFormat(const AudioFormat& format) { if (!format.sampleRate || format.sampleRate > KVMFR_AUDIO_MAX_SAMPLE_RATE || !format.channels || format.channels > KVMFR_AUDIO_STREAM_MAX_CHANNELS || !format.channelMask || (format.channelMask & ~KVMFR_AUDIO_CHANNEL_POSITION_MASK) || ChannelCount(format.channelMask) != format.channels) return false; const bool s16 = format.containerBits == 16 && format.validBits == 16; const bool s24 = format.containerBits == 24 && format.validBits == 24; const uint16_t bytes = static_cast( format.channels * (format.containerBits / 8U)); return (s16 || s24) && format.blockAlign == bytes; } KVMFRAudioFormat CLGMPAudioTransport::WireFormat( const AudioFormat& format) { KVMFRAudioFormat result = {}; result.version = KVMFR_AUDIO_FORMAT_VERSION; result.size = sizeof(result); result.sampleFormat = format.containerBits == 16 ? KVMFR_AUDIO_SAMPLE_S16_LE : KVMFR_AUDIO_SAMPLE_S24_LE; result.sampleRate = format.sampleRate; result.channelMask = format.channelMask; result.channelCount = format.channels; result.validBits = format.validBits; result.containerBits = format.containerBits; result.blockAlign = format.blockAlign; return result; } KVMFRAudioStreamState CLGMPAudioTransport::WireState( AudioStreamState state) { switch (state) { case AudioStreamState::ACQUIRED: return KVMFR_AUDIO_STREAM_ACQUIRED; case AudioStreamState::PAUSED: return KVMFR_AUDIO_STREAM_PAUSED; case AudioStreamState::RUNNING: return KVMFR_AUDIO_STREAM_RUNNING; default: return KVMFR_AUDIO_STREAM_STOPPED; } } KVMFRStreamDescriptor CLGMPAudioTransport::WireDescriptor( const LGMPStreamDescriptor& descriptor) { KVMFRStreamDescriptor wire = {}; wire.magic = descriptor.magic; wire.version = descriptor.version; wire.size = descriptor.size; wire.offset = descriptor.offset; wire.regionSize = descriptor.regionSize; wire.direction = descriptor.direction; wire.policy = descriptor.policy; wire.slotCount = descriptor.slotCount; wire.slotSize = descriptor.slotSize; return wire; } bool CLGMPAudioTransport::InitializeStreams() { const LGMPStreamConfig hostToClient = { LGMP_STREAM_HOST_TO_CLIENT, LGMP_STREAM_RELIABLE_FIFO, KVMFR_AUDIO_STREAM_SLOT_COUNT, static_cast(KVMFR_AUDIO_STREAM_SLOT_BYTES), }; LGMP_STATUS status = m_host.CreateStream( hostToClient, &m_hostToClientStream); if (status != LGMP_OK) { DEBUG_ERROR("Failed to create host-to-client audio stream: %s", lgmpStatusString(status)); return false; } const LGMPStreamConfig clientToHost = { LGMP_STREAM_CLIENT_TO_HOST, LGMP_STREAM_RELIABLE_FIFO, KVMFR_AUDIO_STREAM_SLOT_COUNT, static_cast(KVMFR_AUDIO_STREAM_SLOT_BYTES), }; status = m_host.CreateStream(clientToHost, &m_clientToHostStream); if (status != LGMP_OK) { DEBUG_ERROR("Failed to create client-to-host audio stream: %s", lgmpStatusString(status)); lgmpHostStreamFree(&m_hostToClientStream); return false; } LGMPStreamDescriptor descriptor = {}; lgmpHostStreamGetDescriptor(m_hostToClientStream, &descriptor); m_hostToClientDescriptor = WireDescriptor(descriptor); lgmpHostStreamGetDescriptor(m_clientToHostStream, &descriptor); m_clientToHostDescriptor = WireDescriptor(descriptor); return true; } void CLGMPAudioTransport::FreeStreams() { lgmpHostStreamFree(&m_clientToHostStream); lgmpHostStreamFree(&m_hostToClientStream); m_clientToHostDescriptor = {}; m_hostToClientDescriptor = {}; } DWORD CALLBACK CLGMPAudioTransport::InboundThreadProc(void * context) { static_cast(context)->InboundThread(); return 0; } bool CLGMPAudioTransport::StartInboundWorker() { m_inboundStopEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); m_inboundWakeEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); m_inboundPollTimer = CreateWaitableTimerExW(nullptr, nullptr, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS); if (!m_inboundPollTimer) m_inboundPollTimer = CreateWaitableTimerExW( nullptr, nullptr, 0, TIMER_ALL_ACCESS); if (!m_inboundStopEvent || !m_inboundWakeEvent || !m_inboundPollTimer) { DEBUG_ERROR_HR(GetLastError(), "Failed to create LGMP audio worker resources"); if (m_inboundPollTimer) CloseHandle(m_inboundPollTimer); if (m_inboundWakeEvent) CloseHandle(m_inboundWakeEvent); if (m_inboundStopEvent) CloseHandle(m_inboundStopEvent); m_inboundPollTimer = nullptr; m_inboundWakeEvent = nullptr; m_inboundStopEvent = nullptr; return false; } m_inboundThread = CreateThread(nullptr, 0, InboundThreadProc, this, 0, nullptr); if (!m_inboundThread) { DEBUG_ERROR_HR(GetLastError(), "Failed to create LGMP audio worker"); CloseHandle(m_inboundPollTimer); CloseHandle(m_inboundWakeEvent); CloseHandle(m_inboundStopEvent); m_inboundPollTimer = nullptr; m_inboundWakeEvent = nullptr; m_inboundStopEvent = nullptr; return false; } return true; } void CLGMPAudioTransport::StopInboundWorker() { HANDLE thread = nullptr; { CSRWExclusiveLock lock(m_lock); m_inboundStopping = true; thread = m_inboundThread; if (m_inboundStopEvent) SetEvent(m_inboundStopEvent); } if (thread) WaitForSingleObject(thread, INFINITE); CSRWExclusiveLock lock(m_lock); if (m_inboundThread) CloseHandle(m_inboundThread); if (m_inboundPollTimer) CloseHandle(m_inboundPollTimer); if (m_inboundWakeEvent) CloseHandle(m_inboundWakeEvent); if (m_inboundStopEvent) CloseHandle(m_inboundStopEvent); m_inboundThread = nullptr; m_inboundPollTimer = nullptr; m_inboundWakeEvent = nullptr; m_inboundStopEvent = nullptr; } void CLGMPAudioTransport::InboundThread() { DWORD avTask = 0; HANDLE avTaskHandle = AvSetMmThreadCharacteristicsW(L"Pro Audio", &avTask); if (avTaskHandle && !AvSetMmThreadPriority(avTaskHandle, AVRT_PRIORITY_HIGH)) DEBUG_WARN("Failed to raise audio ingress MMCSS priority: %lu", GetLastError()); LGMPStreamPollState streamPoll = {}; LGMPStreamPollConfig pollConfig = {}; pollConfig.spinCount = 64U; pollConfig.minWaitUs = 25U; pollConfig.maxWaitUs = 250U; const LGMP_STATUS pollStatus = lgmpStreamPollInit(&streamPoll, pollConfig); if (pollStatus != LGMP_OK) { DEBUG_ERROR("Failed to initialize LGMP audio polling: %s", lgmpStatusString(pollStatus)); if (avTaskHandle) AvRevertMmThreadCharacteristics(avTaskHandle); return; } const HANDLE idleWaits[] = { m_inboundStopEvent, m_inboundWakeEvent }; const HANDLE activeWaits[] = { m_inboundStopEvent, m_inboundWakeEvent, m_inboundPollTimer }; for (;;) { if (WaitForSingleObject(m_inboundStopEvent, 0) == WAIT_OBJECT_0) break; uint64_t before = 0; uint64_t hostBefore = 0; bool active = false; bool playbackCreditAcquired = false; { CSRWSharedLock lock(m_lock); before = m_inboundActivitySerial; hostBefore = m_hostSequence; active = m_ownerState != OwnerState::NONE; } if (active) { DrainInbound(); { CSRWExclusiveLock lock(m_lock); const bool hadPlaybackCredit = m_playbackCreditValid; PumpState(); CheckPlaybackCredit(); playbackCreditAcquired = !hadPlaybackCredit && m_playbackCreditValid; } NotifyPlaybackReady(); } { CSRWSharedLock lock(m_lock); if (before != m_inboundActivitySerial || hostBefore != m_hostSequence || playbackCreditAcquired) lgmpStreamPollActivity(&streamPoll); active = m_ownerState != OwnerState::NONE; } if (!active) { const DWORD wait = WaitForMultipleObjects( _countof(idleWaits), idleWaits, FALSE, INFINITE); if (wait == WAIT_OBJECT_0) break; if (wait != WAIT_OBJECT_0 + 1) { DEBUG_ERROR_HR(GetLastError(), "LGMP audio worker wait failed"); break; } lgmpStreamPollActivity(&streamPoll); continue; } const uint32_t waitUs = lgmpStreamPollIdle(&streamPoll); if (!waitUs) continue; if (!ArmAudioPollTimer(m_inboundPollTimer, waitUs)) { DEBUG_ERROR_HR(GetLastError(), "Failed to arm LGMP audio timer"); break; } const DWORD wait = WaitForMultipleObjects( _countof(activeWaits), activeWaits, FALSE, INFINITE); if (wait == WAIT_OBJECT_0) break; if (wait == WAIT_OBJECT_0 + 1) { lgmpStreamPollActivity(&streamPoll); continue; } if (wait != WAIT_OBJECT_0 + 2) { DEBUG_ERROR_HR(GetLastError(), "LGMP audio worker wait failed"); break; } } if (avTaskHandle) AvRevertMmThreadCharacteristics(avTaskHandle); } bool CLGMPAudioTransport::Initialize() { CSRWExclusiveLock lifecycleLock(m_lifecycleLock); CSRWExclusiveLock lock(m_lock); if (m_initialized) return true; // LGMP shared allocations are monotonic. Reserve conservatively before // the first one so ordinary shared-memory exhaustion cannot strand a // partial optional layout. A later local handle allocation can still fail // with LGMP_ERR_NO_MEM, but that is independent of shared capacity. if (m_host.Available() < AUDIO_SHARED_MEMORY_PREFLIGHT) { DEBUG_WARN("LGMP audio requires at least %zu bytes of available " "shared memory", AUDIO_SHARED_MEMORY_PREFLIGHT); return false; } if (!InitializeStreams()) return false; LGMP_STATUS status = LGMP_OK; for (ResponseMemory& response : m_responseMemory) { status = m_host.Allocate(sizeof(KVMFRAudioStreamResponse), &response.memory); if (status != LGMP_OK) break; memset(lgmpHostMemPtr(response.memory), 0, sizeof(KVMFRAudioStreamResponse)); } if (status != LGMP_OK) { DEBUG_ERROR("Failed to allocate audio response memory: %s", lgmpStatusString(status)); for (ResponseMemory& response : m_responseMemory) lgmpHostMemFree(&response.memory); FreeStreams(); return false; } m_state.generation = 1; m_initialized = true; m_inboundStopping = false; if (!StartInboundWorker()) { m_initialized = false; for (ResponseMemory& response : m_responseMemory) lgmpHostMemFree(&response.memory); FreeStreams(); return false; } return true; } bool CLGMPAudioTransport::BindStreams(uint32_t clientID) { LGMP_STATUS status = lgmpHostStreamBind( m_hostToClientStream, clientID, nullptr); if (status != LGMP_OK) { DEBUG_WARN("Failed to bind host-to-client audio stream for client " "%u: %s", clientID, lgmpStatusString(status)); return false; } status = lgmpHostStreamBind(m_clientToHostStream, clientID, nullptr); if (status == LGMP_OK) return true; DEBUG_WARN("Failed to bind client-to-host audio stream for client " "%u: %s", clientID, lgmpStatusString(status)); const LGMP_STATUS rollback = lgmpHostStreamUnbind(m_hostToClientStream); if (rollback != LGMP_OK && rollback != LGMP_ERR_STREAM_UNBOUND) DEBUG_WARN("Failed to roll back the audio stream binding: %s", lgmpStatusString(rollback)); return false; } bool CLGMPAudioTransport::TryUnbindStreams() { bool complete = true; for (PLGMPHostStream stream : { m_clientToHostStream, m_hostToClientStream }) { if (!stream) continue; const LGMP_STATUS status = lgmpHostStreamUnbind(stream); if (status == LGMP_ERR_STREAM_BUSY) complete = false; else if (status != LGMP_OK && status != LGMP_ERR_STREAM_UNBOUND) { Fail("lgmpHostStreamUnbind", status); complete = false; } } return complete; } void CLGMPAudioTransport::ForceUnbindStreams() { for (PLGMPHostStream stream : { m_clientToHostStream, m_hostToClientStream }) { if (!stream) continue; const LGMP_STATUS status = lgmpHostStreamForceUnbind(stream); if (status != LGMP_OK && status != LGMP_ERR_STREAM_UNBOUND) DEBUG_WARN("Failed to force-unbind an audio stream: %s", lgmpStatusString(status)); } } bool CLGMPAudioTransport::ValidateClaim( const KVMFRAudioClaim& claim) const { if (claim.messageType != KVMFR_MESSAGE_AUDIO_CLAIM || claim.version != KVMFR_AUDIO_CONTROL_VERSION || claim.size != sizeof(claim) || !claim.requestGeneration || claim.flags != KVMFR_AUDIO_CLAIM_BIND || !claim.claimToken || !claim.protocolMin || claim.protocolMin > claim.protocolMax || claim.protocolMin > KVMFR_AUDIO_PROTOCOL_MAX || claim.protocolMax < KVMFR_AUDIO_PROTOCOL_MIN || (claim.clientCapabilities & ~AUDIO_CAPABILITY_MASK) || !(claim.clientCapabilities & AUDIO_DIRECTION_MASK) || claim.reserved0) return false; for (uint64_t reserved : claim.reserved) if (reserved) return false; return true; } bool CLGMPAudioTransport::ResponsePending(uint32_t clientID, const KVMFRAudioStreamResponse& candidate) const { for (const PendingResponse& pending : m_pendingResponse) if (pending.valid && pending.clientID == clientID && pending.response.requestGeneration == candidate.requestGeneration && pending.response.claimToken == candidate.claimToken && pending.response.result == candidate.result) return true; for (const ResponseMemory& response : m_responseMemory) if (response.valid && response.clientID == clientID && response.requestGeneration == candidate.requestGeneration && response.claimToken == candidate.claimToken && response.result == candidate.result) return true; return false; } bool CLGMPAudioTransport::QueueResponse(uint32_t clientID, const KVMFRAudioStreamResponse& response) { if (ResponsePending(clientID, response)) return true; PendingResponse * available = nullptr; PendingResponse * sameClient = nullptr; for (PendingResponse& pending : m_pendingResponse) { if (pending.valid && pending.clientID == clientID) { if (pending.response.result == KVMFR_AUDIO_RESPONSE_BOUND) return false; if (!sameClient) sameClient = &pending; continue; } if (!pending.valid && !available) available = &pending; } if (sameClient) available = sameClient; if (!available) return false; available->valid = true; available->clientID = clientID; available->response = response; return true; } void CLGMPAudioTransport::CleanupResponseMemory() { for (ResponseMemory& response : m_responseMemory) { if (!response.valid || m_control.PayloadPending(response.memory)) continue; response.valid = false; response.clientID = 0; response.requestGeneration = 0; response.claimToken = 0; response.result = 0; } } void CLGMPAudioTransport::PumpResponses() { CleanupResponseMemory(); for (PendingResponse& pending : m_pendingResponse) { if (!pending.valid) continue; ResponseMemory * available = nullptr; for (ResponseMemory& response : m_responseMemory) if (response.memory && !response.valid) { available = &response; break; } if (!available) return; memcpy(lgmpHostMemPtr(available->memory), &pending.response, sizeof(pending.response)); WCWrite::Flush(); unsigned recipients = 0; const LGMP_STATUS status = m_control.PostForClient( KVMFR_AUDIO_CONTROL_UDATA, available->memory, pending.clientID, recipients); if (status == LGMP_ERR_QUEUE_FULL) return; if (status != LGMP_OK) { Fail("lgmpHostQueuePostForClients(audio)", status); return; } const bool bound = pending.response.result == KVMFR_AUDIO_RESPONSE_BOUND; if (recipients) { available->valid = true; available->clientID = pending.clientID; available->requestGeneration = pending.response.requestGeneration; available->claimToken = pending.response.claimToken; available->result = pending.response.result; if (bound && m_ownerState == OwnerState::BOUND && m_ownerClientID == pending.clientID && m_ownerGeneration == pending.response.requestGeneration && m_ownerToken == pending.response.claimToken) { m_responseAdmitted = true; m_statusDirty = true; } } else if (bound && m_ownerState == OwnerState::BOUND && m_ownerClientID == pending.clientID && m_ownerGeneration == pending.response.requestGeneration && m_ownerToken == pending.response.claimToken) BeginRelease("audio claimant disappeared"); pending = {}; } } bool CLGMPAudioTransport::OwnerSubscribed() const { if (!m_ownerClientID) return false; uint32_t clients[LGMP_MAX_CLIENTS] = {}; unsigned count = 0; const LGMP_STATUS status = m_control.GetClientIDs(clients, count); if (status != LGMP_OK) { DEBUG_WARN("Failed to query audio owner subscription: %s", lgmpStatusString(status)); return true; } for (unsigned index = 0; index < count; ++index) if (clients[index] == m_ownerClientID) return true; return false; } void CLGMPAudioTransport::RenewLease() { m_ownerDeadline = GetTickCount64() + KVMFR_AUDIO_OWNER_LEASE_MS; } void CLGMPAudioTransport::ReleasePendingCapture() { if (!m_pendingCapture.valid) return; const LGMP_STATUS status = lgmpHostStreamReadRelease( m_clientToHostStream, &m_pendingCapture.buffer); if (status != LGMP_OK && status != LGMP_ERR_STREAM_UNBOUND && status != LGMP_ERR_STREAM_STALE) Fail("lgmpHostStreamReadRelease(audio)", status); m_pendingCapture = {}; m_inboundActivitySerial = NextSerial(m_inboundActivitySerial); } void CLGMPAudioTransport::CancelPlaybackCredit() { if (!m_playbackCreditValid) return; const LGMP_STATUS status = lgmpHostStreamWriteCancel( m_hostToClientStream, &m_playbackCredit); if (status != LGMP_OK && status != LGMP_ERR_STREAM_UNBOUND && status != LGMP_ERR_STREAM_STALE) { DEBUG_WARN("Failed to cancel an audio playback reservation: %s", lgmpStatusString(status)); m_failed = true; } m_playbackCredit = {}; m_playbackCreditValid = false; } void CLGMPAudioTransport::ClearBlockedPlayback(bool notify) { const bool blocked = m_playbackBlocked; CancelPlaybackCredit(); m_playbackBlocked = false; m_blockedPlayback = {}; m_blockedPlaybackSize = 0; if (notify && blocked) m_playbackReadyPending = true; } bool CLGMPAudioTransport::RememberBlockedPlayback( const AudioPacket& packet, size_t size) { if (m_playbackBlocked) return size == m_blockedPlaybackSize && SamePacket(packet, m_blockedPlayback); m_playbackBlocked = true; m_blockedPlayback = packet; m_blockedPlaybackSize = size; return true; } void CLGMPAudioTransport::BeginRelease(const char * reason) { if (m_ownerState == OwnerState::NONE) return; if (m_ownerState != OwnerState::RELEASING) { DEBUG_INFO("Audio owner %u generation %u releasing: %s", m_ownerClientID, m_ownerGeneration, reason); m_ownerState = OwnerState::RELEASING; m_releaseGeneration = m_state.generation; m_deactivationIssued = false; m_statusDirty = false; m_announcedPlayback = 0; m_announcedCapture = 0; ClearBlockedPlayback(true); if (!m_captureCallbackInFlight) ReleasePendingCapture(); if (!m_inboundStopping && m_inboundWakeEvent) SetEvent(m_inboundWakeEvent); for (PendingResponse& pending : m_pendingResponse) if (pending.valid && pending.clientID == m_ownerClientID && pending.response.result == KVMFR_AUDIO_RESPONSE_BOUND) pending = {}; } } void CLGMPAudioTransport::ResetOwner() { ClearBlockedPlayback(false); for (ClaimRecord& record : m_claimRecord) if (record.valid && record.clientID == m_ownerClientID && record.requestGeneration == m_ownerGeneration && record.claimToken == m_ownerToken) { record.closed = true; break; } m_ownerState = OwnerState::NONE; m_ownerClientID = 0; m_ownerGeneration = 0; m_clientCapabilities = 0; m_ownerToken = 0; m_ownerDeadline = 0; m_releaseGeneration = 0; m_responseAdmitted = false; m_clientReady = false; m_activationIssued = false; m_deactivationIssued = false; m_ownerResponse = {}; m_statusDirty = false; m_announcedPlayback = 0; m_announcedCapture = 0; m_hostSequence = 0; m_nextClientSequence = 0; m_clientSequenceKnown = false; m_captureDiscontinuity = false; m_captureCallbackInFlight = false; m_pendingCapture = {}; m_ownerSerial = NextSerial(m_ownerSerial); } void CLGMPAudioTransport::MaintainOwner(uint64_t now) { if (m_ownerState == OwnerState::NONE) return; if (m_ownerState != OwnerState::RELEASING) { if (!OwnerSubscribed()) BeginRelease("audio pointer subscriber disappeared"); else if (m_pendingCapture.valid) RenewLease(); else if (now >= m_ownerDeadline) BeginRelease("audio owner lease expired"); } if (m_ownerState != OwnerState::RELEASING) return; const bool streamsUnbound = TryUnbindStreams(); const bool inactive = !m_started || !m_activationIssued || (m_stateKnown && !m_state.active && m_state.generation != m_releaseGeneration); if (streamsUnbound && inactive) { DEBUG_INFO("Audio owner %u generation %u released", m_ownerClientID, m_ownerGeneration); ResetOwner(); } } void CLGMPAudioTransport::InitializeOwner(uint32_t clientID, const KVMFRAudioClaim& claim) { m_ownerState = OwnerState::BOUND; m_ownerClientID = clientID; m_ownerGeneration = claim.requestGeneration; m_clientCapabilities = claim.clientCapabilities; m_ownerToken = claim.claimToken; m_responseAdmitted = false; m_clientReady = false; m_activationIssued = false; m_deactivationIssued = false; m_statusDirty = true; m_announcedPlayback = 0; m_announcedCapture = 0; m_hostSequence = 0; m_nextClientSequence = 0; m_clientSequenceKnown = false; m_captureDiscontinuity = false; ClearBlockedPlayback(false); m_pendingCapture = {}; m_ownerSerial = NextSerial(m_ownerSerial); RenewLease(); if (!m_inboundStopping && m_inboundWakeEvent) SetEvent(m_inboundWakeEvent); } void CLGMPAudioTransport::HandleClaim( uint32_t clientID, const KVMFRAudioClaim& claim) { CSRWExclusiveLock lock(m_lock); if (!m_initialized) return; KVMFRAudioStreamResponse response = {}; response.magic = KVMFR_AUDIO_RESPONSE_MAGIC; response.version = KVMFR_AUDIO_CONTROL_VERSION; response.size = sizeof(response); response.requestGeneration = claim.requestGeneration; response.boundClientID = clientID; response.endpointGeneration = m_state.generation; response.claimToken = claim.claimToken; ClaimRecord * claimRecord = nullptr; ClaimRecord * claimSlot = nullptr; for (ClaimRecord& record : m_claimRecord) { if (record.valid && record.clientID == clientID) claimRecord = &record; if (!record.valid && !claimSlot) claimSlot = &record; } if (claimRecord) claimSlot = claimRecord; const bool claimValid = ValidateClaim(claim); if (!claimValid) response.result = KVMFR_AUDIO_RESPONSE_INCOMPATIBLE; else if (m_failed || !m_started || !m_stateKnown) response.result = KVMFR_AUDIO_RESPONSE_ERROR; else if (m_ownerState != OwnerState::NONE) { if (m_ownerState != OwnerState::RELEASING && m_ownerClientID == clientID && m_ownerGeneration == claim.requestGeneration && m_ownerToken == claim.claimToken) { RenewLease(); response = m_ownerResponse; } else response.result = KVMFR_AUDIO_RESPONSE_BUSY; } else if (claimRecord && claimRecord->requestGeneration == claim.requestGeneration && claimRecord->claimToken == claim.claimToken) { if (claimRecord->response.result == KVMFR_AUDIO_RESPONSE_ERROR) response = claimRecord->response; else response.result = KVMFR_AUDIO_RESPONSE_BUSY; } else if (claimRecord && !RequestNewer( claim.requestGeneration, claimRecord->requestGeneration)) response.result = KVMFR_AUDIO_RESPONSE_BUSY; else if (!claimSlot) { DEBUG_WARN("Audio claim history is full for client %u", clientID); response.result = KVMFR_AUDIO_RESPONSE_ERROR; } else if (BindStreams(clientID)) { InitializeOwner(clientID, claim); response.result = KVMFR_AUDIO_RESPONSE_BOUND; response.protocolVersion = KVMFR_AUDIO_PROTOCOL_MAX; response.hostToClient = m_hostToClientDescriptor; response.clientToHost = m_clientToHostDescriptor; m_ownerResponse = response; claimSlot->valid = true; claimSlot->closed = false; claimSlot->clientID = clientID; claimSlot->requestGeneration = claim.requestGeneration; claimSlot->claimToken = claim.claimToken; claimSlot->response = response; DEBUG_INFO("Audio owner %u generation %u admitted", m_ownerClientID, m_ownerGeneration); } else response.result = KVMFR_AUDIO_RESPONSE_ERROR; if (claimValid && claimSlot && response.result == KVMFR_AUDIO_RESPONSE_ERROR && (!claimRecord || RequestNewer(claim.requestGeneration, claimRecord->requestGeneration))) { claimSlot->valid = true; claimSlot->closed = true; claimSlot->clientID = clientID; claimSlot->requestGeneration = claim.requestGeneration; claimSlot->claimToken = claim.claimToken; claimSlot->response = response; } if (!QueueResponse(clientID, response)) { DEBUG_WARN("Audio response queue is full for client %u", clientID); if (response.result == KVMFR_AUDIO_RESPONSE_BOUND && m_ownerClientID == clientID) BeginRelease("audio response queue exhausted"); } PumpResponses(); } KVMFRAudioStatus CLGMPAudioTransport::WireStatus() const { KVMFRAudioStatus status = {}; status.version = KVMFR_AUDIO_STATUS_VERSION; status.size = sizeof(status); status.protocolVersion = KVMFR_AUDIO_PROTOCOL_MAX; status.requestGeneration = m_ownerGeneration; status.capabilities = m_state.capabilities & m_clientCapabilities; status.ownerClientID = m_ownerClientID; status.ownerGeneration = m_ownerGeneration; status.endpointGeneration = m_state.generation; status.playbackGeneration = m_state.playback.generation; status.captureGeneration = m_state.capture.generation; status.playbackState = WireState(m_state.playback.state); status.captureState = WireState(m_state.capture.state); if (m_state.playback.formatValid && ValidFormat(m_state.playback.format)) status.playbackFormat = WireFormat(m_state.playback.format); if (m_state.capture.formatValid && ValidFormat(m_state.capture.format)) status.captureFormat = WireFormat(m_state.capture.format); if (m_state.available) status.flags |= KVMFR_AUDIO_STATUS_AVAILABLE; if (m_ownerState != OwnerState::NONE) status.flags |= KVMFR_AUDIO_STATUS_HAS_OWNER; if (status.capabilities & KVMFR_AUDIO_CAP_PLAYBACK) status.flags |= KVMFR_AUDIO_STATUS_PLAYBACK_AVAILABLE; if (status.capabilities & KVMFR_AUDIO_CAP_CAPTURE) status.flags |= KVMFR_AUDIO_STATUS_CAPTURE_AVAILABLE; if ((status.capabilities & KVMFR_AUDIO_CAP_PLAYBACK) && m_state.available && m_state.active && m_state.playback.state != AudioStreamState::UNAVAILABLE && m_state.playback.state != AudioStreamState::STOPPED) status.flags |= KVMFR_AUDIO_STATUS_PLAYBACK_ACTIVE; if ((status.capabilities & KVMFR_AUDIO_CAP_CAPTURE) && m_state.available && m_state.active && m_state.capture.state != AudioStreamState::UNAVAILABLE && m_state.capture.state != AudioStreamState::STOPPED) status.flags |= KVMFR_AUDIO_STATUS_CAPTURE_ACTIVE; return status; } bool CLGMPAudioTransport::CommitMessage(LGMPStreamBuffer& buffer, 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) { const uint32_t bytes = static_cast( sizeof(KVMFRAudioMessage) + length); if (!buffer.data || buffer.capacity < bytes) { const LGMP_STATUS cancel = lgmpHostStreamWriteCancel( m_hostToClientStream, &buffer); if (cancel != LGMP_OK) DEBUG_WARN("Failed to cancel an undersized audio stream " "reservation: %s", lgmpStatusString(cancel)); Fail("undersized host-to-client audio stream slot", LGMP_ERR_INVALID_SIZE); return false; } KVMFRAudioMessage message = {}; message.version = KVMFR_AUDIO_MESSAGE_VERSION; message.size = sizeof(message); message.type = type; message.streamGeneration = streamGeneration; message.flags = flags; message.sequence = m_hostSequence; message.position = position; message.timeNs = timeNs; message.rateQ32 = rateQ32; message.frames = frames; message.length = length; memcpy(buffer.data, &message, sizeof(message)); if (length) WCWrite::Copy(static_cast(buffer.data) + sizeof(message), payload, length); const LGMP_STATUS status = lgmpHostStreamWriteCommit( m_hostToClientStream, &buffer, bytes); if (status == LGMP_OK) { ++m_hostSequence; return true; } if (status == LGMP_ERR_STREAM_UNBOUND || status == LGMP_ERR_STREAM_STALE) { BeginRelease("host-to-client audio stream binding lost"); return false; } Fail("lgmpHostStreamWriteCommit(audio)", status); return false; } bool CLGMPAudioTransport::WriteMessage(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 (m_playbackCreditValid) return false; LGMPStreamBuffer buffer = {}; const LGMP_STATUS status = lgmpHostStreamWriteAcquire( m_hostToClientStream, &buffer); if (status == LGMP_ERR_STREAM_FULL) return false; if (status == LGMP_ERR_STREAM_UNBOUND || status == LGMP_ERR_STREAM_STALE) { BeginRelease("host-to-client audio stream binding lost"); return false; } if (status != LGMP_OK) { Fail("lgmpHostStreamWriteAcquire(audio)", status); return false; } return CommitMessage(buffer, type, streamGeneration, flags, position, timeNs, rateQ32, frames, payload, length); } void CLGMPAudioTransport::PumpState() { if (m_ownerState != OwnerState::BOUND || !m_responseAdmitted || !m_clientReady || !m_stateKnown || m_failed || m_playbackCreditValid) return; const bool playbackCurrent = m_state.available && m_state.active && (m_clientCapabilities & KVMFR_AUDIO_CAP_PLAYBACK) && (m_state.capabilities & AUDIO_CAP_PLAYBACK) && m_state.playback.formatValid && ValidFormat(m_state.playback.format) && m_state.playback.state != AudioStreamState::UNAVAILABLE && m_state.playback.state != AudioStreamState::STOPPED; if (m_announcedPlayback && (!playbackCurrent || m_announcedPlayback != m_state.playback.generation)) { if (!WriteMessage(KVMFR_AUDIO_MESSAGE_PLAYBACK_STOP, m_announcedPlayback, 0, 0, 0, 0, 0, nullptr, 0)) return; m_announcedPlayback = 0; } const bool captureCurrent = m_state.available && m_state.active && (m_clientCapabilities & KVMFR_AUDIO_CAP_CAPTURE) && (m_state.capabilities & AUDIO_CAP_CAPTURE) && m_state.capture.formatValid && ValidFormat(m_state.capture.format) && m_state.capture.state != AudioStreamState::UNAVAILABLE && m_state.capture.state != AudioStreamState::STOPPED; if (m_announcedCapture && (!captureCurrent || m_announcedCapture != m_state.capture.generation)) { if (!WriteMessage(KVMFR_AUDIO_MESSAGE_CAPTURE_STOP, m_announcedCapture, 0, 0, 0, 0, 0, nullptr, 0)) return; m_announcedCapture = 0; } if (playbackCurrent && m_announcedPlayback != m_state.playback.generation) { const KVMFRAudioFormat format = WireFormat(m_state.playback.format); if (!WriteMessage(KVMFR_AUDIO_MESSAGE_PLAYBACK_START, m_state.playback.generation, 0, 0, 0, 0, 0, &format, sizeof(format))) return; m_announcedPlayback = m_state.playback.generation; } if (captureCurrent && m_announcedCapture != m_state.capture.generation) { const KVMFRAudioFormat format = WireFormat(m_state.capture.format); if (!WriteMessage(KVMFR_AUDIO_MESSAGE_CAPTURE_START, m_state.capture.generation, 0, 0, 0, 0, 0, &format, sizeof(format))) return; m_announcedCapture = m_state.capture.generation; } if (!m_statusDirty) return; const KVMFRAudioStatus status = WireStatus(); if (WriteMessage(KVMFR_AUDIO_MESSAGE_STATUS, 0, 0, 0, 0, 0, 0, &status, sizeof(status))) m_statusDirty = false; } void CLGMPAudioTransport::CheckPlaybackCredit() { if (!m_playbackBlocked || m_playbackCreditValid || m_playbackReadyPending || m_statusDirty || m_ownerState != OwnerState::BOUND || !m_responseAdmitted || !m_clientReady || !m_stateKnown || !m_state.available || !m_state.active || !(m_clientCapabilities & KVMFR_AUDIO_CAP_PLAYBACK) || !(m_state.capabilities & AUDIO_CAP_PLAYBACK) || m_blockedPlayback.endpointGeneration != m_state.generation || m_blockedPlayback.streamGeneration != m_state.playback.generation || !m_state.playback.formatValid || m_state.playback.state != AudioStreamState::RUNNING || !SameFormat(m_blockedPlayback.format, m_state.playback.format) || m_announcedPlayback != m_blockedPlayback.streamGeneration) return; m_playbackCredit = {}; const LGMP_STATUS status = lgmpHostStreamWriteAcquire( m_hostToClientStream, &m_playbackCredit); if (status == LGMP_ERR_STREAM_FULL) return; if (status == LGMP_ERR_STREAM_UNBOUND || status == LGMP_ERR_STREAM_STALE) { BeginRelease("host-to-client audio credit binding lost"); return; } if (status != LGMP_OK) { Fail("lgmpHostStreamWriteAcquire(audio credit)", status); return; } const size_t required = sizeof(KVMFRAudioMessage) + m_blockedPlaybackSize; if (!m_playbackCredit.data || m_playbackCredit.capacity < required) { const LGMP_STATUS cancel = lgmpHostStreamWriteCancel( m_hostToClientStream, &m_playbackCredit); m_playbackCredit = {}; if (cancel != LGMP_OK) DEBUG_WARN("Failed to cancel an undersized audio credit: %s", lgmpStatusString(cancel)); Fail("undersized host-to-client audio stream slot", LGMP_ERR_INVALID_SIZE); return; } m_playbackCreditValid = true; m_playbackReadyPending = true; } bool CLGMPAudioTransport::ParseInbound( const LGMPStreamBuffer& buffer, KVMFRAudioMessage& message) const { 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 || message.length > (std::numeric_limits::max)() - sizeof(message)) return false; return buffer.size == sizeof(message) + message.length; } bool CLGMPAudioTransport::PendingCaptureCurrent() const { if (!m_pendingCapture.valid) return false; const AudioPacket& packet = m_pendingCapture.packet; return m_stateKnown && m_state.available && m_state.active && (m_clientCapabilities & KVMFR_AUDIO_CAP_CAPTURE) && (m_state.capabilities & AUDIO_CAP_CAPTURE) && m_state.capture.formatValid && ValidFormat(m_state.capture.format) && m_state.capture.state == AudioStreamState::RUNNING && packet.endpointGeneration == m_state.generation && packet.streamGeneration == m_state.capture.generation && SameFormat(packet.format, m_state.capture.format); } bool CLGMPAudioTransport::PrepareCapture( const KVMFRAudioMessage& message, const LGMPStreamBuffer& buffer) { if (!m_stateKnown || !m_state.available || !m_state.active || !(m_state.capabilities & AUDIO_CAP_CAPTURE) || !m_state.capture.formatValid || !ValidFormat(m_state.capture.format) || m_state.capture.state != AudioStreamState::RUNNING || message.streamGeneration != m_state.capture.generation) return false; const bool silent = (message.flags & KVMFR_AUDIO_MESSAGE_SILENT) != 0; const uint32_t bytes = message.frames * m_state.capture.format.blockAlign; if (bytes > KVMFR_AUDIO_PCM_BYTES || (silent ? message.length != 0 : message.length != bytes)) return false; AudioPacket packet; packet.endpointGeneration = m_state.generation; packet.streamGeneration = message.streamGeneration; if (message.flags & KVMFR_AUDIO_MESSAGE_DISCONTINUITY) packet.flags |= AUDIO_PACKET_DISCONTINUITY; if (message.flags & KVMFR_AUDIO_MESSAGE_SILENT) packet.flags |= AUDIO_PACKET_SILENT; if (message.flags & KVMFR_AUDIO_MESSAGE_CLOCK_VALID) packet.flags |= AUDIO_PACKET_CLOCK_VALID; if (message.flags & KVMFR_AUDIO_MESSAGE_CLOCK_STABLE) packet.flags |= AUDIO_PACKET_CLOCK_STABLE; if (m_captureDiscontinuity || (m_clientSequenceKnown && message.sequence != m_nextClientSequence) || (!m_clientSequenceKnown && message.sequence != 0)) packet.flags |= AUDIO_PACKET_DISCONTINUITY; packet.frames = message.frames; packet.position = message.position; packet.timeNs = message.timeNs; packet.rateQ32 = message.rateQ32; packet.format = m_state.capture.format; m_pendingCapture.valid = true; m_pendingCapture.buffer = buffer; m_pendingCapture.packet = packet; m_pendingCapture.data = silent ? nullptr : static_cast(buffer.data) + sizeof(KVMFRAudioMessage); m_pendingCapture.size = message.length; m_pendingCapture.readySerial = m_captureReadySerial; m_nextClientSequence = message.sequence + 1; m_clientSequenceKnown = true; m_inboundActivitySerial = NextSerial(m_inboundActivitySerial); return true; } void CLGMPAudioTransport::DrainInbound() { for (unsigned count = 0; count < INBOUND_DRAIN_MAX; ++count) { CSRWSharedLock callbackLock(m_callbackLock); IAudioTarget * target = nullptr; AudioClockFeedback feedback; bool callCapture = false; bool callClock = false; uint64_t ownerSerial = 0; { CSRWExclusiveLock lock(m_lock); if (m_ownerState == OwnerState::RELEASING) { ReleasePendingCapture(); LGMPStreamBuffer buffer = {}; const LGMP_STATUS status = lgmpHostStreamReadPeek( m_clientToHostStream, &buffer); if (status == LGMP_ERR_STREAM_EMPTY || status == LGMP_ERR_STREAM_UNBOUND || status == LGMP_ERR_STREAM_STALE) return; if (status != LGMP_OK) { Fail("lgmpHostStreamReadPeek(audio release)", status); return; } const LGMP_STATUS release = lgmpHostStreamReadRelease( m_clientToHostStream, &buffer); if (release != LGMP_OK) { Fail("lgmpHostStreamReadRelease(audio release)", release); return; } m_inboundActivitySerial = NextSerial(m_inboundActivitySerial); continue; } if (m_ownerState != OwnerState::BOUND || !m_responseAdmitted || m_failed) return; target = m_started ? m_target : nullptr; ownerSerial = m_ownerSerial; if (m_pendingCapture.valid) { if (!PendingCaptureCurrent()) { ReleasePendingCapture(); m_captureDiscontinuity = true; continue; } if (m_pendingCapture.readySerial == m_captureReadySerial) return; m_pendingCapture.readySerial = m_captureReadySerial; callCapture = true; } else { LGMPStreamBuffer buffer = {}; const LGMP_STATUS status = lgmpHostStreamReadPeek( m_clientToHostStream, &buffer); if (status == LGMP_ERR_STREAM_EMPTY) return; if (status == LGMP_ERR_STREAM_UNBOUND || status == LGMP_ERR_STREAM_STALE) { BeginRelease("client-to-host audio stream binding lost"); return; } if (status != LGMP_OK) { Fail("lgmpHostStreamReadPeek(audio)", status); return; } KVMFRAudioMessage message = {}; if (!ParseInbound(buffer, message)) { const LGMP_STATUS release = lgmpHostStreamReadRelease( m_clientToHostStream, &buffer); if (release != LGMP_OK) Fail("lgmpHostStreamReadRelease(audio invalid)", release); BeginRelease("malformed client-to-host audio record"); return; } const bool sequenceGap = (m_clientSequenceKnown && message.sequence != m_nextClientSequence) || (!m_clientSequenceKnown && message.sequence != 0); const auto consume = [&]() -> bool { const LGMP_STATUS release = lgmpHostStreamReadRelease( m_clientToHostStream, &buffer); if (release != LGMP_OK) { Fail("lgmpHostStreamReadRelease(audio)", release); return false; } m_nextClientSequence = message.sequence + 1; m_clientSequenceKnown = true; m_inboundActivitySerial = NextSerial(m_inboundActivitySerial); return true; }; if (!m_clientReady && message.type != KVMFR_AUDIO_MESSAGE_KEEPALIVE && message.type != KVMFR_AUDIO_MESSAGE_RELEASE) { if (!consume()) return; BeginRelease("audio data received before client readiness"); return; } switch (message.type) { case KVMFR_AUDIO_MESSAGE_CAPTURE_DATA: { const bool clockStable = (message.flags & KVMFR_AUDIO_MESSAGE_CLOCK_STABLE) != 0; const bool clockValid = (message.flags & KVMFR_AUDIO_MESSAGE_CLOCK_VALID) != 0; if (!(m_clientCapabilities & KVMFR_AUDIO_CAP_CAPTURE) || (message.flags & ~WIRE_PACKET_FLAG_MASK) || (clockStable && !clockValid) || (!clockValid && (message.position || message.timeNs || message.rateQ32)) || !message.streamGeneration || !message.frames || message.frames > KVMFR_AUDIO_MAX_FRAMES_PER_PACKET) { consume(); BeginRelease("invalid capture audio record"); return; } RenewLease(); const bool captureCurrent = m_stateKnown && m_state.available && m_state.active && (m_state.capabilities & AUDIO_CAP_CAPTURE) && m_state.capture.formatValid && ValidFormat(m_state.capture.format) && m_state.capture.state == AudioStreamState::RUNNING && message.streamGeneration == m_state.capture.generation; if (captureCurrent) { const bool silent = (message.flags & KVMFR_AUDIO_MESSAGE_SILENT) != 0; const uint32_t bytes = message.frames * m_state.capture.format.blockAlign; if (bytes > KVMFR_AUDIO_PCM_BYTES || (silent ? message.length != 0 : message.length != bytes)) { consume(); BeginRelease("invalid capture audio payload"); return; } } if (!PrepareCapture(message, buffer)) { consume(); m_captureDiscontinuity = true; continue; } if (sequenceGap) m_pendingCapture.packet.flags |= AUDIO_PACKET_DISCONTINUITY; callCapture = true; break; } case KVMFR_AUDIO_MESSAGE_CLOCK_FEEDBACK: { const uint32_t validFlags = KVMFR_AUDIO_MESSAGE_CLOCK_VALID | KVMFR_AUDIO_MESSAGE_CLOCK_STABLE | KVMFR_AUDIO_MESSAGE_DISCONTINUITY; KVMFRAudioClockFeedback wire = {}; if (message.length == sizeof(wire)) memcpy(&wire, static_cast(buffer.data) + sizeof(message), sizeof(wire)); const bool valid = (m_clientCapabilities & KVMFR_AUDIO_CAP_CLOCK_FEEDBACK) && message.flags && !(message.flags & ~validFlags) && (message.flags & KVMFR_AUDIO_MESSAGE_CLOCK_VALID) && (!(message.flags & KVMFR_AUDIO_MESSAGE_CLOCK_STABLE) || (message.flags & KVMFR_AUDIO_MESSAGE_CLOCK_VALID)) && message.streamGeneration && !message.frames && message.length == sizeof(wire) && !wire.reserved && wire.targetRateQ32; if (!valid) { consume(); BeginRelease("invalid audio clock feedback"); return; } const bool current = m_stateKnown && m_state.available && m_state.active && (m_state.capabilities & AUDIO_CAP_CLOCK_FEEDBACK) && message.streamGeneration == m_state.playback.generation; if (!consume()) return; RenewLease(); if (sequenceGap) m_captureDiscontinuity = true; if (!current) continue; feedback.endpointGeneration = m_state.generation; feedback.playbackGeneration = message.streamGeneration; feedback.position = message.position; feedback.timeNs = message.timeNs; feedback.rateQ32 = message.rateQ32; feedback.targetRateQ32 = wire.targetRateQ32; feedback.flags = AUDIO_CLOCK_VALID; if (message.flags & KVMFR_AUDIO_MESSAGE_CLOCK_STABLE) feedback.flags |= AUDIO_CLOCK_STABLE; if (sequenceGap || (message.flags & KVMFR_AUDIO_MESSAGE_DISCONTINUITY)) feedback.flags |= AUDIO_CLOCK_DISCONTINUITY; callClock = true; break; } case KVMFR_AUDIO_MESSAGE_KEEPALIVE: case KVMFR_AUDIO_MESSAGE_RELEASE: { const bool empty = !message.streamGeneration && !message.flags && !message.position && !message.timeNs && !message.rateQ32 && !message.frames && !message.length; if (!consume()) return; if (!empty) { BeginRelease("invalid audio owner control record"); return; } if (message.type == KVMFR_AUDIO_MESSAGE_RELEASE) { BeginRelease("client audio release"); return; } if (!m_clientReady) { if (sequenceGap) { BeginRelease("invalid initial audio keepalive sequence"); return; } m_clientReady = true; DEBUG_INFO("Audio owner %u generation %u is ready", m_ownerClientID, m_ownerGeneration); } if (sequenceGap) m_captureDiscontinuity = true; RenewLease(); continue; } default: consume(); BeginRelease("unsupported client-to-host audio record"); return; } } m_captureCallbackInFlight = target && callCapture; } AudioResult result = AudioResult::UNAVAILABLE; if (target && callCapture) result = target->SendCapture(m_pendingCapture.packet, m_pendingCapture.data, m_pendingCapture.size); else if (target && callClock) result = target->SetClockFeedback(feedback); { CSRWExclusiveLock lock(m_lock); if (callCapture) m_captureCallbackInFlight = false; if (ownerSerial != m_ownerSerial || m_ownerState != OwnerState::BOUND) { if (m_pendingCapture.valid) ReleasePendingCapture(); return; } if (callClock) { if (result == AudioResult::FAILED) BeginRelease("audio clock target failed"); continue; } if (!callCapture || !m_pendingCapture.valid) continue; if (!PendingCaptureCurrent()) { ReleasePendingCapture(); m_captureDiscontinuity = true; continue; } if (result == AudioResult::BUSY) return; const LGMP_STATUS release = lgmpHostStreamReadRelease( m_clientToHostStream, &m_pendingCapture.buffer); if (release != LGMP_OK) { m_pendingCapture = {}; Fail("lgmpHostStreamReadRelease(audio capture)", release); return; } m_pendingCapture = {}; m_inboundActivitySerial = NextSerial(m_inboundActivitySerial); m_captureDiscontinuity = result != AudioResult::ACCEPTED; if (result == AudioResult::FAILED || result == AudioResult::REJECTED) { BeginRelease("audio capture target failed"); return; } } } } void CLGMPAudioTransport::ProcessActivation() { CSRWSharedLock callbackLock(m_callbackLock); IAudioTarget * target = nullptr; bool activate = false; uint64_t ownerSerial = 0; { CSRWSharedLock lock(m_lock); if (!m_started || !m_target || m_failed || m_ownerState == OwnerState::NONE) return; if (m_ownerState == OwnerState::BOUND && m_responseAdmitted && m_clientReady && !m_activationIssued) activate = true; else if (m_ownerState != OwnerState::RELEASING || !m_activationIssued || m_deactivationIssued) return; target = m_target; ownerSerial = m_ownerSerial; } const AudioResult result = target->SetAudioActive(activate); CSRWExclusiveLock lock(m_lock); if (ownerSerial != m_ownerSerial || m_ownerState == OwnerState::NONE) return; if (activate) { if (m_ownerState != OwnerState::BOUND || m_activationIssued) return; if (result == AudioResult::ACCEPTED) m_activationIssued = true; else if (result == AudioResult::FAILED || result == AudioResult::REJECTED) BeginRelease("audio endpoint activation failed"); } else if (m_ownerState == OwnerState::RELEASING && (result == AudioResult::ACCEPTED || result == AudioResult::UNAVAILABLE || result == AudioResult::STALE)) m_deactivationIssued = true; } void CLGMPAudioTransport::NotifyPlaybackReady() { CSRWSharedLock callbackLock(m_callbackLock); IAudioTarget * target = nullptr; { CSRWExclusiveLock lock(m_lock); if (!m_playbackReadyPending) return; m_playbackReadyPending = false; if (m_started) target = m_target; } if (target) target->PlaybackReceiveReady(); } void CLGMPAudioTransport::NotifyFailure() { CSRWSharedLock callbackLock(m_callbackLock); IAudioTarget * target = nullptr; { CSRWExclusiveLock lock(m_lock); if (!m_failed || m_failureNotified) return; m_failureNotified = true; if (m_started) target = m_target; } if (target) target->AudioFailed(); } void CLGMPAudioTransport::Fail( const char * operation, LGMP_STATUS status) { if (!m_failed) DEBUG_ERROR("%s Failed (Audio): %s", operation, lgmpStatusString(status)); ClearBlockedPlayback(true); m_failed = true; } void CLGMPAudioTransport::Process() { { CSRWExclusiveLock lock(m_lock); if (!m_initialized) return; PumpResponses(); } { CSRWExclusiveLock lock(m_lock); MaintainOwner(GetTickCount64()); PumpResponses(); PumpState(); CheckPlaybackCredit(); } ProcessActivation(); NotifyPlaybackReady(); NotifyFailure(); } bool CLGMPAudioTransport::Start(IAudioTarget& target) { CSRWExclusiveLock lifecycleLock(m_lifecycleLock); CSRWExclusiveLock callbackLock(m_callbackLock); CSRWExclusiveLock lock(m_lock); if (!m_initialized || m_failed) return false; if (m_started) return m_target == ⌖ m_target = ⌖ m_started = true; m_stateKnown = false; m_failureNotified = false; return true; } void CLGMPAudioTransport::Stop() { CSRWExclusiveLock lifecycleLock(m_lifecycleLock); CSRWExclusiveLock callbackLock(m_callbackLock); CSRWExclusiveLock lock(m_lock); if (!m_started && !m_target) return; if (m_ownerState != OwnerState::NONE) BeginRelease("audio source stopped"); else ClearBlockedPlayback(false); m_started = false; m_target = nullptr; m_playbackReadyPending = false; } void CLGMPAudioTransport::TransportStop() { CSRWExclusiveLock lifecycleLock(m_lifecycleLock); StopInboundWorker(); CSRWExclusiveLock callbackLock(m_callbackLock); CSRWExclusiveLock lock(m_lock); m_started = false; m_target = nullptr; m_playbackReadyPending = false; ClearBlockedPlayback(false); ReleasePendingCapture(); ForceUnbindStreams(); ResetOwner(); for (PendingResponse& response : m_pendingResponse) response = {}; for (ClaimRecord& record : m_claimRecord) record = {}; } void CLGMPAudioTransport::AudioState( const AudioEndpointStatus& state) { CSRWExclusiveLock lock(m_lock); if (!m_started) return; if (m_playbackBlocked) { const bool playbackCurrent = state.available && state.active && (m_clientCapabilities & KVMFR_AUDIO_CAP_PLAYBACK) && (state.capabilities & AUDIO_CAP_PLAYBACK) && state.playback.formatValid && ValidFormat(state.playback.format) && state.playback.state == AudioStreamState::RUNNING && state.generation == m_blockedPlayback.endpointGeneration && state.playback.generation == m_blockedPlayback.streamGeneration && SameFormat(state.playback.format, m_blockedPlayback.format); if (!playbackCurrent) ClearBlockedPlayback(true); } m_state = state; m_stateKnown = true; if (m_ownerState == OwnerState::BOUND && m_responseAdmitted) m_statusDirty = true; if (!m_inboundStopping && m_inboundWakeEvent) SetEvent(m_inboundWakeEvent); } AudioResult CLGMPAudioTransport::SendPlayback( const AudioPacket& packet, const uint8_t * data, size_t size) { if ((packet.flags & ~AUDIO_PACKET_FLAG_MASK) || ((packet.flags & AUDIO_PACKET_CLOCK_STABLE) && !(packet.flags & AUDIO_PACKET_CLOCK_VALID)) || !packet.frames || packet.frames > AUDIO_MAX_FRAMES_PER_PACKET || !ValidFormat(packet.format)) return AudioResult::REJECTED; const size_t pcmBytes = static_cast(packet.frames) * packet.format.blockAlign; const bool silent = (packet.flags & AUDIO_PACKET_SILENT) != 0; if (pcmBytes > AUDIO_MAX_PCM_BYTES || (silent ? size != 0 : (size != pcmBytes || !data))) return AudioResult::REJECTED; CSRWExclusiveLock lock(m_lock); if (m_playbackBlocked && (size != m_blockedPlaybackSize || !SamePacket(packet, m_blockedPlayback))) { DEBUG_WARN("Audio playback retry did not match the retained packet"); ClearBlockedPlayback(false); return AudioResult::REJECTED; } if (!m_started) { ClearBlockedPlayback(false); return AudioResult::UNAVAILABLE; } if (m_failed) { ClearBlockedPlayback(false); return AudioResult::FAILED; } if (m_ownerState != OwnerState::BOUND || !(m_clientCapabilities & KVMFR_AUDIO_CAP_PLAYBACK)) { ClearBlockedPlayback(false); return AudioResult::ACCEPTED; } if (!m_responseAdmitted || !m_clientReady || !m_stateKnown) { if (!RememberBlockedPlayback(packet, size)) return AudioResult::REJECTED; return AudioResult::BUSY; } if (!m_state.available || !m_state.active) { ClearBlockedPlayback(false); return AudioResult::UNAVAILABLE; } if (packet.endpointGeneration != m_state.generation || packet.streamGeneration != m_state.playback.generation) { ClearBlockedPlayback(false); return AudioResult::STALE; } if (!(m_state.capabilities & AUDIO_CAP_PLAYBACK) || !m_state.playback.formatValid || m_state.playback.state != AudioStreamState::RUNNING || !SameFormat(packet.format, m_state.playback.format)) { ClearBlockedPlayback(false); return AudioResult::UNAVAILABLE; } KVMFRAudioMessageFlags flags = 0; if (packet.flags & AUDIO_PACKET_DISCONTINUITY) flags |= KVMFR_AUDIO_MESSAGE_DISCONTINUITY; if (packet.flags & AUDIO_PACKET_SILENT) flags |= KVMFR_AUDIO_MESSAGE_SILENT; if (packet.flags & AUDIO_PACKET_CLOCK_VALID) flags |= KVMFR_AUDIO_MESSAGE_CLOCK_VALID; if (packet.flags & AUDIO_PACKET_CLOCK_STABLE) flags |= KVMFR_AUDIO_MESSAGE_CLOCK_STABLE; if (m_playbackCreditValid) { LGMPStreamBuffer buffer = m_playbackCredit; m_playbackCredit = {}; m_playbackCreditValid = false; const bool committed = CommitMessage(buffer, KVMFR_AUDIO_MESSAGE_PLAYBACK_DATA, packet.streamGeneration, flags, packet.position, packet.timeNs, packet.rateQ32, packet.frames, data, static_cast(size)); ClearBlockedPlayback(false); if (committed) return AudioResult::ACCEPTED; if (m_failed) return AudioResult::FAILED; return m_ownerState == OwnerState::BOUND ? AudioResult::BUSY : AudioResult::ACCEPTED; } PumpState(); if (m_failed) return AudioResult::FAILED; if (m_ownerState != OwnerState::BOUND) return AudioResult::ACCEPTED; if (m_statusDirty || m_announcedPlayback != packet.streamGeneration) { if (!RememberBlockedPlayback(packet, size)) return AudioResult::REJECTED; return AudioResult::BUSY; } if (!WriteMessage(KVMFR_AUDIO_MESSAGE_PLAYBACK_DATA, packet.streamGeneration, flags, packet.position, packet.timeNs, packet.rateQ32, packet.frames, data, static_cast(size))) { if (m_failed) return AudioResult::FAILED; if (m_ownerState != OwnerState::BOUND) return AudioResult::ACCEPTED; if (!RememberBlockedPlayback(packet, size)) return AudioResult::REJECTED; return AudioResult::BUSY; } ClearBlockedPlayback(false); return AudioResult::ACCEPTED; } void CLGMPAudioTransport::CaptureReceiveReady() { CSRWExclusiveLock lock(m_lock); if (m_started) { m_captureReadySerial = NextSerial(m_captureReadySerial); if (!m_inboundStopping && m_inboundWakeEvent) SetEvent(m_inboundWakeEvent); } }