mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-11-12 14:57:20 +00:00
[client/idd] audio: support multiple playback clients
Replace the single-owner PCM queues with one shared LGMP SPMC stream. Give each admitted client reliable state and an independent playback cursor, while clients without clock authority resample locally. Extend the audio protocol with per-client epochs and barriers. Keep capture exclusive and bound authority backpressure so slow secondary readers cannot affect the disciplined low-latency path.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -22,7 +22,7 @@
|
||||
|
||||
#include <LGProtocol/KVMFR.h>
|
||||
#ifdef ENABLE_AUDIO
|
||||
#include "common/KVMFRAudio.h"
|
||||
#include <LGProtocol/KVMFRAudio.h>
|
||||
#endif
|
||||
#include <LGProtocol/KVMFRRecovery.h>
|
||||
#include <LGProtocol/LGMPConfig.h>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -23,7 +23,7 @@
|
||||
|
||||
#include "interface/audio.h"
|
||||
|
||||
#include "common/KVMFRAudio.h"
|
||||
#include <LGProtocol/KVMFRAudio.h>
|
||||
#include "common/event.h"
|
||||
|
||||
#include <lgmp/client.h>
|
||||
|
||||
@@ -2419,7 +2419,8 @@ static LG_TransportStatus lgmp_nextPointer(LG_Transport * this,
|
||||
#ifdef ENABLE_AUDIO
|
||||
KVMFRAudioStreamResponse audioResponse;
|
||||
const bool audioResponseValid =
|
||||
tag == KVMFR_AUDIO_CONTROL_TAG && this->audio &&
|
||||
tag == KVMFR_AUDIO_CONTROL_TAG &&
|
||||
message.udata == KVMFR_AUDIO_CONTROL_UDATA && this->audio &&
|
||||
message.size == sizeof(audioResponse);
|
||||
if (audioResponseValid)
|
||||
memcpy(&audioResponse, message.mem, sizeof(audioResponse));
|
||||
|
||||
@@ -1,447 +0,0 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifndef _H_LG_COMMON_KVMFR_AUDIO_
|
||||
#define _H_LG_COMMON_KVMFR_AUDIO_
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LGProtocol/KVMFR.h>
|
||||
#include <LGProtocol/KVMFRStream.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
enum
|
||||
{
|
||||
/* Static protocol support only; KVMFRAudioStatus reports availability. */
|
||||
KVMFR_FEATURE_AUDIO = 1U << 5,
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
KVMFR_MESSAGE_AUDIO_CLAIM = 3,
|
||||
};
|
||||
|
||||
#define KVMFR_AUDIO_PROTOCOL_MIN 1U
|
||||
#define KVMFR_AUDIO_PROTOCOL_MAX 1U
|
||||
#define KVMFR_AUDIO_CONTROL_VERSION 1U
|
||||
#define KVMFR_AUDIO_MESSAGE_VERSION 1U
|
||||
#define KVMFR_AUDIO_FORMAT_VERSION 1U
|
||||
#define KVMFR_AUDIO_STATUS_VERSION 1U
|
||||
#define KVMFR_AUDIO_VOLUME_VERSION 1U
|
||||
|
||||
#define KVMFR_AUDIO_MAX_CHANNELS 18U
|
||||
#define KVMFR_AUDIO_MAX_SAMPLE_RATE 192000U
|
||||
#define KVMFR_AUDIO_MAX_FRAMES_PER_PACKET 2048U
|
||||
#define KVMFR_AUDIO_MAX_BYTES_PER_SAMPLE 4U
|
||||
#define KVMFR_AUDIO_STREAM_MAX_CHANNELS 8U
|
||||
#define KVMFR_AUDIO_STREAM_SLOT_COUNT 16U
|
||||
#define KVMFR_AUDIO_OWNER_LEASE_MS 1000U
|
||||
#define KVMFR_AUDIO_KEEPALIVE_MS 200U
|
||||
#define KVMFR_AUDIO_PCM_BYTES \
|
||||
(KVMFR_AUDIO_MAX_FRAMES_PER_PACKET * \
|
||||
KVMFR_AUDIO_STREAM_MAX_CHANNELS * \
|
||||
KVMFR_AUDIO_MAX_BYTES_PER_SAMPLE)
|
||||
|
||||
enum
|
||||
{
|
||||
KVMFR_AUDIO_CAP_PLAYBACK = 1U << 0,
|
||||
KVMFR_AUDIO_CAP_CAPTURE = 1U << 1,
|
||||
KVMFR_AUDIO_CAP_CLOCK_FEEDBACK = 1U << 2,
|
||||
};
|
||||
|
||||
typedef uint32_t KVMFRAudioCapabilities;
|
||||
|
||||
enum
|
||||
{
|
||||
KVMFR_AUDIO_SAMPLE_S16_LE = 1,
|
||||
/* Signed little-endian samples packed into exactly three bytes. */
|
||||
KVMFR_AUDIO_SAMPLE_S24_LE = 2,
|
||||
KVMFR_AUDIO_SAMPLE_S32_LE = 3,
|
||||
/* IEEE 754 little-endian floating-point samples. */
|
||||
KVMFR_AUDIO_SAMPLE_F32_LE = 4,
|
||||
};
|
||||
|
||||
typedef uint32_t KVMFRAudioSampleFormat;
|
||||
|
||||
enum
|
||||
{
|
||||
KVMFR_AUDIO_SAMPLE_MASK_S16_LE = 1U << 0,
|
||||
KVMFR_AUDIO_SAMPLE_MASK_S24_LE = 1U << 1,
|
||||
KVMFR_AUDIO_SAMPLE_MASK_S32_LE = 1U << 2,
|
||||
KVMFR_AUDIO_SAMPLE_MASK_F32_LE = 1U << 3,
|
||||
};
|
||||
|
||||
typedef uint32_t KVMFRAudioSampleFormatFlags;
|
||||
|
||||
/* These values match the conventional Windows speaker-position mask. */
|
||||
enum
|
||||
{
|
||||
KVMFR_AUDIO_CHANNEL_FRONT_LEFT = 1U << 0,
|
||||
KVMFR_AUDIO_CHANNEL_FRONT_RIGHT = 1U << 1,
|
||||
KVMFR_AUDIO_CHANNEL_FRONT_CENTER = 1U << 2,
|
||||
KVMFR_AUDIO_CHANNEL_LFE = 1U << 3,
|
||||
KVMFR_AUDIO_CHANNEL_BACK_LEFT = 1U << 4,
|
||||
KVMFR_AUDIO_CHANNEL_BACK_RIGHT = 1U << 5,
|
||||
KVMFR_AUDIO_CHANNEL_FRONT_LEFT_OF_CENTER = 1U << 6,
|
||||
KVMFR_AUDIO_CHANNEL_FRONT_RIGHT_OF_CENTER = 1U << 7,
|
||||
KVMFR_AUDIO_CHANNEL_BACK_CENTER = 1U << 8,
|
||||
KVMFR_AUDIO_CHANNEL_SIDE_LEFT = 1U << 9,
|
||||
KVMFR_AUDIO_CHANNEL_SIDE_RIGHT = 1U << 10,
|
||||
KVMFR_AUDIO_CHANNEL_TOP_CENTER = 1U << 11,
|
||||
KVMFR_AUDIO_CHANNEL_TOP_FRONT_LEFT = 1U << 12,
|
||||
KVMFR_AUDIO_CHANNEL_TOP_FRONT_CENTER = 1U << 13,
|
||||
KVMFR_AUDIO_CHANNEL_TOP_FRONT_RIGHT = 1U << 14,
|
||||
KVMFR_AUDIO_CHANNEL_TOP_BACK_LEFT = 1U << 15,
|
||||
KVMFR_AUDIO_CHANNEL_TOP_BACK_CENTER = 1U << 16,
|
||||
KVMFR_AUDIO_CHANNEL_TOP_BACK_RIGHT = 1U << 17,
|
||||
};
|
||||
|
||||
typedef uint32_t KVMFRAudioChannelMask;
|
||||
|
||||
#define KVMFR_AUDIO_CHANNEL_MONO \
|
||||
KVMFR_AUDIO_CHANNEL_FRONT_CENTER
|
||||
#define KVMFR_AUDIO_CHANNEL_STEREO \
|
||||
(KVMFR_AUDIO_CHANNEL_FRONT_LEFT | KVMFR_AUDIO_CHANNEL_FRONT_RIGHT)
|
||||
#define KVMFR_AUDIO_CHANNEL_5POINT1 \
|
||||
(KVMFR_AUDIO_CHANNEL_STEREO | KVMFR_AUDIO_CHANNEL_FRONT_CENTER | \
|
||||
KVMFR_AUDIO_CHANNEL_LFE | KVMFR_AUDIO_CHANNEL_SIDE_LEFT | \
|
||||
KVMFR_AUDIO_CHANNEL_SIDE_RIGHT)
|
||||
#define KVMFR_AUDIO_CHANNEL_7POINT1 \
|
||||
(KVMFR_AUDIO_CHANNEL_5POINT1 | KVMFR_AUDIO_CHANNEL_BACK_LEFT | \
|
||||
KVMFR_AUDIO_CHANNEL_BACK_RIGHT)
|
||||
#define KVMFR_AUDIO_CHANNEL_POSITION_MASK UINT32_C(0x0003FFFF)
|
||||
|
||||
/* Interleaved samples and volume entries use ascending set-bit order from
|
||||
* channelMask. channelCount must equal the number of set mask bits and may
|
||||
* cover all 18 standard positions even when an implementation advertises a
|
||||
* lower active channel limit. */
|
||||
typedef struct KVMFRAudioFormat
|
||||
{
|
||||
uint16_t version;
|
||||
uint16_t size;
|
||||
KVMFRAudioSampleFormat sampleFormat;
|
||||
uint32_t sampleRate;
|
||||
KVMFRAudioChannelMask channelMask;
|
||||
uint16_t channelCount;
|
||||
uint16_t validBits;
|
||||
uint16_t containerBits;
|
||||
uint16_t blockAlign;
|
||||
uint32_t reserved[2];
|
||||
}
|
||||
KVMFRAudioFormat;
|
||||
|
||||
enum
|
||||
{
|
||||
KVMFR_AUDIO_MESSAGE_STATUS = 1,
|
||||
|
||||
KVMFR_AUDIO_MESSAGE_PLAYBACK_START,
|
||||
KVMFR_AUDIO_MESSAGE_PLAYBACK_STOP,
|
||||
KVMFR_AUDIO_MESSAGE_PLAYBACK_DATA,
|
||||
KVMFR_AUDIO_MESSAGE_PLAYBACK_VOLUME,
|
||||
KVMFR_AUDIO_MESSAGE_PLAYBACK_MUTE,
|
||||
|
||||
KVMFR_AUDIO_MESSAGE_CAPTURE_START,
|
||||
KVMFR_AUDIO_MESSAGE_CAPTURE_STOP,
|
||||
KVMFR_AUDIO_MESSAGE_CAPTURE_DATA,
|
||||
KVMFR_AUDIO_MESSAGE_CAPTURE_VOLUME,
|
||||
KVMFR_AUDIO_MESSAGE_CAPTURE_MUTE,
|
||||
|
||||
KVMFR_AUDIO_MESSAGE_CLOCK_FEEDBACK,
|
||||
KVMFR_AUDIO_MESSAGE_KEEPALIVE,
|
||||
KVMFR_AUDIO_MESSAGE_RELEASE,
|
||||
};
|
||||
|
||||
typedef uint32_t KVMFRAudioMessageType;
|
||||
|
||||
enum
|
||||
{
|
||||
KVMFR_AUDIO_MESSAGE_DISCONTINUITY = 1U << 0,
|
||||
KVMFR_AUDIO_MESSAGE_SILENT = 1U << 1,
|
||||
KVMFR_AUDIO_MESSAGE_CLOCK_STABLE = 1U << 2,
|
||||
KVMFR_AUDIO_MESSAGE_MUTED = 1U << 3,
|
||||
KVMFR_AUDIO_MESSAGE_CLOCK_VALID = 1U << 4,
|
||||
};
|
||||
|
||||
typedef uint32_t KVMFRAudioMessageFlags;
|
||||
|
||||
/*
|
||||
* Every record uses one of the two ordinary LGMP streams:
|
||||
*
|
||||
* host-to-client: STATUS, PLAYBACK_*, and CAPTURE start/stop/control;
|
||||
* client-to-host: CAPTURE_DATA, CLOCK_FEEDBACK, KEEPALIVE, and RELEASE.
|
||||
*
|
||||
* Any payload immediately follows this fixed header. START carries one
|
||||
* KVMFRAudioFormat, VOLUME carries KVMFRAudioVolume, and DATA
|
||||
* carries frames * blockAlign bytes. The position and timeNs fields describe
|
||||
* the first frame of DATA or the correlated clock sample in CLOCK_FEEDBACK.
|
||||
* Times are monotonic nanoseconds in the publisher's clock domain and may be
|
||||
* compared only by difference unless that domain is otherwise known.
|
||||
*/
|
||||
typedef struct KVMFRAudioMessage
|
||||
{
|
||||
uint16_t version;
|
||||
uint16_t size;
|
||||
KVMFRAudioMessageType type;
|
||||
uint64_t streamGeneration;
|
||||
KVMFRAudioMessageFlags flags;
|
||||
uint32_t reserved0;
|
||||
uint64_t sequence;
|
||||
uint64_t position;
|
||||
int64_t timeNs;
|
||||
/* Unsigned 32.32 frames per second; zero means unavailable. */
|
||||
uint64_t rateQ32;
|
||||
uint32_t frames;
|
||||
uint32_t length;
|
||||
}
|
||||
KVMFRAudioMessage;
|
||||
|
||||
/*
|
||||
* Type-specific payload and header rules:
|
||||
*
|
||||
* STATUS:
|
||||
* host-to-client, KVMFRAudioStatus payload, zero streamGeneration/frames.
|
||||
* PLAYBACK_START / CAPTURE_START:
|
||||
* host-to-client, KVMFRAudioFormat payload, nonzero streamGeneration and
|
||||
* zero frames. CLOCK_VALID marks an optional source-clock correlation.
|
||||
* PLAYBACK_STOP / CAPTURE_STOP:
|
||||
* host-to-client, no payload, nonzero streamGeneration and zero frames.
|
||||
* PLAYBACK_DATA / CAPTURE_DATA:
|
||||
* the direction implied by the name, nonzero streamGeneration, and between
|
||||
* one and KVMFR_AUDIO_MAX_FRAMES_PER_PACKET frames. Without SILENT, length
|
||||
* is frames * blockAlign, does not exceed KVMFR_AUDIO_PCM_BYTES, and the
|
||||
* payload is interleaved PCM. With SILENT, length is zero and no PCM payload
|
||||
* is present.
|
||||
* PLAYBACK_VOLUME / CAPTURE_VOLUME:
|
||||
* host-to-client, KVMFRAudioVolume payload, nonzero streamGeneration and
|
||||
* zero frames.
|
||||
* PLAYBACK_MUTE / CAPTURE_MUTE:
|
||||
* host-to-client, no payload, nonzero streamGeneration, zero frames, and
|
||||
* MUTED set or clear for the new state.
|
||||
* CLOCK_FEEDBACK:
|
||||
* client-to-host, KVMFRAudioClockFeedback payload, the playback generation,
|
||||
* zero frames, and CLOCK_VALID set.
|
||||
* KEEPALIVE / RELEASE:
|
||||
* client-to-host, no payload and zero streamGeneration/frames.
|
||||
*
|
||||
* Sequence starts at zero and increments for every record on each LGMP stream
|
||||
* while bound. A producer that drops a full realtime record leaves a sequence
|
||||
* gap and sets DISCONTINUITY on its next record.
|
||||
*/
|
||||
|
||||
/* Payload for CLOCK_FEEDBACK. The message clock describes the playback
|
||||
* device; targetRateQ32 is the requested source rate. */
|
||||
typedef struct KVMFRAudioClockFeedback
|
||||
{
|
||||
uint64_t targetRateQ32;
|
||||
uint64_t reserved;
|
||||
}
|
||||
KVMFRAudioClockFeedback;
|
||||
|
||||
/* Fixed-size payload for PLAYBACK_VOLUME and CAPTURE_VOLUME. Values use the
|
||||
* full unsigned 16-bit range, where zero is silent and UINT16_MAX is unity.
|
||||
* Entries beyond channelCount must be zero. */
|
||||
typedef struct KVMFRAudioVolume
|
||||
{
|
||||
uint16_t version;
|
||||
uint16_t size;
|
||||
uint16_t channelCount;
|
||||
uint16_t reserved;
|
||||
uint16_t values[KVMFR_AUDIO_MAX_CHANNELS];
|
||||
}
|
||||
KVMFRAudioVolume;
|
||||
|
||||
#define KVMFR_AUDIO_STREAM_SLOT_BYTES \
|
||||
(sizeof(KVMFRAudioMessage) + KVMFR_AUDIO_PCM_BYTES)
|
||||
|
||||
enum
|
||||
{
|
||||
KVMFR_AUDIO_STATUS_AVAILABLE = 1U << 0,
|
||||
KVMFR_AUDIO_STATUS_HAS_OWNER = 1U << 1,
|
||||
KVMFR_AUDIO_STATUS_PLAYBACK_AVAILABLE = 1U << 2,
|
||||
KVMFR_AUDIO_STATUS_CAPTURE_AVAILABLE = 1U << 3,
|
||||
KVMFR_AUDIO_STATUS_PLAYBACK_ACTIVE = 1U << 4,
|
||||
KVMFR_AUDIO_STATUS_CAPTURE_ACTIVE = 1U << 5,
|
||||
};
|
||||
|
||||
typedef uint32_t KVMFRAudioStatusFlags;
|
||||
|
||||
enum
|
||||
{
|
||||
KVMFR_AUDIO_STREAM_STOPPED = 0,
|
||||
KVMFR_AUDIO_STREAM_ACQUIRED = 1,
|
||||
KVMFR_AUDIO_STREAM_PAUSED = 2,
|
||||
KVMFR_AUDIO_STREAM_RUNNING = 3,
|
||||
};
|
||||
|
||||
typedef uint32_t KVMFRAudioStreamState;
|
||||
|
||||
typedef struct KVMFRAudioStatus
|
||||
{
|
||||
uint16_t version;
|
||||
uint16_t size;
|
||||
KVMFRAudioStatusFlags flags;
|
||||
uint32_t protocolVersion;
|
||||
uint32_t requestGeneration;
|
||||
KVMFRAudioCapabilities capabilities;
|
||||
uint32_t ownerClientID;
|
||||
uint32_t ownerGeneration;
|
||||
uint32_t reserved0;
|
||||
uint64_t endpointGeneration;
|
||||
uint64_t playbackGeneration;
|
||||
uint64_t captureGeneration;
|
||||
KVMFRAudioStreamState playbackState;
|
||||
KVMFRAudioStreamState captureState;
|
||||
KVMFRAudioFormat playbackFormat;
|
||||
KVMFRAudioFormat captureFormat;
|
||||
}
|
||||
KVMFRAudioStatus;
|
||||
|
||||
enum
|
||||
{
|
||||
KVMFR_AUDIO_CLAIM_BIND = 1U << 0,
|
||||
};
|
||||
|
||||
typedef uint32_t KVMFRAudioClaimFlags;
|
||||
|
||||
/* Sent through the existing LGMP client-control message path. flags must be
|
||||
* exactly KVMFR_AUDIO_CLAIM_BIND in protocol v1. Audio support is statically
|
||||
* advertised in KVMFR features; runtime availability is not a prerequisite
|
||||
* for binding these streams. */
|
||||
typedef struct KVMFRAudioClaim
|
||||
{
|
||||
uint32_t messageType;
|
||||
uint16_t version;
|
||||
uint16_t size;
|
||||
uint32_t requestGeneration;
|
||||
KVMFRAudioClaimFlags flags;
|
||||
uint64_t claimToken;
|
||||
uint32_t protocolMin;
|
||||
uint32_t protocolMax;
|
||||
KVMFRAudioCapabilities clientCapabilities;
|
||||
uint32_t reserved0;
|
||||
uint64_t reserved[3];
|
||||
}
|
||||
KVMFRAudioClaim;
|
||||
|
||||
/*
|
||||
* Descriptor responses share the existing pointer queue. The high udata word
|
||||
* identifies this record; the low word remains zero for cursor compatibility.
|
||||
*/
|
||||
#define KVMFR_AUDIO_CONTROL_TAG UINT32_C(0x41554431)
|
||||
#define KVMFR_AUDIO_CONTROL_UDATA (UINT64_C(0x41554431) << 32)
|
||||
#define KVMFR_AUDIO_RESPONSE_MAGIC UINT32_C(0x41554452)
|
||||
#define KVMFR_AUDIO_CURSOR_COMPAT_BYTES 28U
|
||||
|
||||
#if defined(__cplusplus)
|
||||
static_assert(sizeof(KVMFRCursor) == KVMFR_AUDIO_CURSOR_COMPAT_BYTES,
|
||||
"KVMFR cursor compatibility layout changed");
|
||||
#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
|
||||
_Static_assert(sizeof(KVMFRCursor) == KVMFR_AUDIO_CURSOR_COMPAT_BYTES,
|
||||
"KVMFR cursor compatibility layout changed");
|
||||
#endif
|
||||
|
||||
enum
|
||||
{
|
||||
KVMFR_AUDIO_RESPONSE_BOUND = 1,
|
||||
KVMFR_AUDIO_RESPONSE_BUSY = 2,
|
||||
KVMFR_AUDIO_RESPONSE_INCOMPATIBLE = 3,
|
||||
KVMFR_AUDIO_RESPONSE_ERROR = 4,
|
||||
};
|
||||
|
||||
typedef uint16_t KVMFRAudioResponseResult;
|
||||
|
||||
/* Exactly one response result is returned. A BOUND response selects a
|
||||
* nonzero protocolVersion and makes both descriptors valid. */
|
||||
typedef struct KVMFRAudioStreamResponse
|
||||
{
|
||||
uint8_t cursorCompatibility[28];
|
||||
uint32_t magic;
|
||||
uint16_t version;
|
||||
uint16_t size;
|
||||
uint32_t requestGeneration;
|
||||
uint32_t boundClientID;
|
||||
KVMFRAudioResponseResult result;
|
||||
uint16_t protocolVersion;
|
||||
uint64_t endpointGeneration;
|
||||
uint64_t claimToken;
|
||||
KVMFRStreamDescriptor hostToClient;
|
||||
KVMFRStreamDescriptor clientToHost;
|
||||
}
|
||||
KVMFRAudioStreamResponse;
|
||||
|
||||
#if defined(__cplusplus)
|
||||
static_assert(sizeof(KVMFRAudioFormat) == 32,
|
||||
"KVMFR audio format layout changed");
|
||||
static_assert(sizeof(KVMFRAudioMessage) == 64,
|
||||
"KVMFR audio message layout changed");
|
||||
static_assert(sizeof(KVMFRAudioClockFeedback) == 16,
|
||||
"KVMFR audio clock feedback layout changed");
|
||||
static_assert(sizeof(KVMFRAudioVolume) == 44,
|
||||
"KVMFR audio volume layout changed");
|
||||
static_assert(sizeof(KVMFRAudioStatus) == 128,
|
||||
"KVMFR audio status layout changed");
|
||||
static_assert(offsetof(KVMFRAudioStatus, endpointGeneration) == 32,
|
||||
"KVMFR audio endpoint generation layout changed");
|
||||
static_assert(offsetof(KVMFRAudioStatus, playbackFormat) == 64,
|
||||
"KVMFR audio status format layout changed");
|
||||
static_assert(sizeof(KVMFRAudioClaim) == 64,
|
||||
"KVMFR audio claim must fit one LGMP control message");
|
||||
static_assert(offsetof(KVMFRAudioStreamResponse, magic) == 28,
|
||||
"KVMFR audio pointer compatibility layout changed");
|
||||
static_assert(offsetof(KVMFRAudioStreamResponse, endpointGeneration) == 48,
|
||||
"KVMFR audio response generation layout changed");
|
||||
static_assert(offsetof(KVMFRAudioStreamResponse, claimToken) == 56,
|
||||
"KVMFR audio response token layout changed");
|
||||
static_assert(offsetof(KVMFRAudioStreamResponse, hostToClient) == 64,
|
||||
"KVMFR audio stream discovery layout changed");
|
||||
static_assert(sizeof(KVMFRAudioStreamResponse) == 128,
|
||||
"KVMFR audio stream response layout changed");
|
||||
#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
|
||||
_Static_assert(sizeof(KVMFRAudioFormat) == 32,
|
||||
"KVMFR audio format layout changed");
|
||||
_Static_assert(sizeof(KVMFRAudioMessage) == 64,
|
||||
"KVMFR audio message layout changed");
|
||||
_Static_assert(sizeof(KVMFRAudioClockFeedback) == 16,
|
||||
"KVMFR audio clock feedback layout changed");
|
||||
_Static_assert(sizeof(KVMFRAudioVolume) == 44,
|
||||
"KVMFR audio volume layout changed");
|
||||
_Static_assert(sizeof(KVMFRAudioStatus) == 128,
|
||||
"KVMFR audio status layout changed");
|
||||
_Static_assert(offsetof(KVMFRAudioStatus, endpointGeneration) == 32,
|
||||
"KVMFR audio endpoint generation layout changed");
|
||||
_Static_assert(offsetof(KVMFRAudioStatus, playbackFormat) == 64,
|
||||
"KVMFR audio status format layout changed");
|
||||
_Static_assert(sizeof(KVMFRAudioClaim) == 64,
|
||||
"KVMFR audio claim must fit one LGMP control message");
|
||||
_Static_assert(offsetof(KVMFRAudioStreamResponse, magic) == 28,
|
||||
"KVMFR audio pointer compatibility layout changed");
|
||||
_Static_assert(offsetof(KVMFRAudioStreamResponse, endpointGeneration) == 48,
|
||||
"KVMFR audio response generation layout changed");
|
||||
_Static_assert(offsetof(KVMFRAudioStreamResponse, claimToken) == 56,
|
||||
"KVMFR audio response token layout changed");
|
||||
_Static_assert(offsetof(KVMFRAudioStreamResponse, hostToClient) == 64,
|
||||
"KVMFR audio stream discovery layout changed");
|
||||
_Static_assert(sizeof(KVMFRAudioStreamResponse) == 128,
|
||||
"KVMFR audio stream response layout changed");
|
||||
#endif
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,9 +21,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "CSRWLock.h"
|
||||
#include "common/KVMFRAudio.h"
|
||||
#include "transport/IAudioSource.h"
|
||||
|
||||
#include <LGProtocol/KVMFRAudio.h>
|
||||
#include <LGProtocol/LGMPConfig.h>
|
||||
|
||||
#include <Windows.h>
|
||||
@@ -33,6 +33,7 @@
|
||||
|
||||
extern "C" {
|
||||
#include "lgmp/host.h"
|
||||
#include "lgmp/spmc.h"
|
||||
#include "lgmp/stream.h"
|
||||
}
|
||||
|
||||
@@ -40,21 +41,40 @@ class CLGMPControl;
|
||||
class CLGMPHost;
|
||||
class CLGMPTransport;
|
||||
|
||||
class CLGMPAudioTransport final : public IAudioSource
|
||||
class CLGMPAudio final : public IAudioSource
|
||||
{
|
||||
private:
|
||||
static constexpr unsigned RESPONSE_COUNT = LGMP_Q_POINTER_LEN;
|
||||
static constexpr unsigned CLAIM_HISTORY_COUNT = 1024;
|
||||
static constexpr uint32_t INVALID_SLOT = UINT32_MAX;
|
||||
static constexpr unsigned INBOUND_DRAIN_MAX =
|
||||
KVMFR_AUDIO_STREAM_SLOT_COUNT * 2U;
|
||||
static constexpr uint64_t AUTHORITY_STALL_MS = 20U;
|
||||
static constexpr uint64_t CAPTURE_REVOKE_ACK_MS =
|
||||
KVMFR_AUDIO_OWNER_LEASE_MS;
|
||||
|
||||
enum class OwnerState
|
||||
enum class SubscriberState
|
||||
{
|
||||
NONE,
|
||||
BOUND,
|
||||
RELEASING,
|
||||
};
|
||||
|
||||
enum class PumpStage
|
||||
{
|
||||
IDLE,
|
||||
STATUS,
|
||||
PLAYBACK_STOP,
|
||||
CAPTURE_STOP,
|
||||
PLAYBACK_START,
|
||||
PLAYBACK_VOLUME,
|
||||
PLAYBACK_MUTE,
|
||||
CAPTURE_START,
|
||||
CAPTURE_VOLUME,
|
||||
CAPTURE_MUTE,
|
||||
BARRIER,
|
||||
};
|
||||
|
||||
struct PendingResponse
|
||||
{
|
||||
bool valid = false;
|
||||
@@ -85,6 +105,8 @@ private:
|
||||
struct PendingCapture
|
||||
{
|
||||
bool valid = false;
|
||||
uint32_t subscriber = INVALID_SLOT;
|
||||
uint64_t subscriberSerial = 0;
|
||||
LGMPStreamBuffer buffer = {};
|
||||
AudioPacket packet = {};
|
||||
const uint8_t * data = nullptr;
|
||||
@@ -92,13 +114,65 @@ private:
|
||||
uint64_t readySerial = 0;
|
||||
};
|
||||
|
||||
struct Subscriber
|
||||
{
|
||||
PLGMPHostStream hostControl = nullptr;
|
||||
PLGMPHostStream clientControl = nullptr;
|
||||
KVMFRStreamDescriptor hostControlDescriptor = {};
|
||||
KVMFRStreamDescriptor clientControlDescriptor = {};
|
||||
|
||||
SubscriberState state = SubscriberState::NONE;
|
||||
uint32_t clientID = 0;
|
||||
uint32_t requestGeneration = 0;
|
||||
uint32_t clientCapabilities = 0;
|
||||
KVMFRAudioSubscriptionFlags subscriptions = 0;
|
||||
KVMFRAudioCapabilities grantedCapabilities = 0;
|
||||
uint32_t bindingEpoch = 0;
|
||||
uint32_t roleEpoch = 0;
|
||||
uint32_t playbackReaderID = 0;
|
||||
uint32_t playbackReaderEpoch = 0;
|
||||
uint32_t hostControlEpoch = 0;
|
||||
uint32_t clientControlEpoch = 0;
|
||||
uint32_t captureEpoch = 0;
|
||||
uint64_t claimToken = 0;
|
||||
uint64_t serial = 0;
|
||||
uint64_t deadline = 0;
|
||||
bool responseAdmitted = false;
|
||||
bool subscriptionReceived = false;
|
||||
bool ready = false;
|
||||
bool awaitingAck = false;
|
||||
bool forceUnbind = false;
|
||||
bool feedbackRevoked = false;
|
||||
bool captureRevoking = false;
|
||||
uint64_t captureRevokeRevision = 0;
|
||||
uint64_t captureRevokeDeadline = 0;
|
||||
|
||||
uint64_t hostSequence = 0;
|
||||
uint64_t nextControlSequence = 0;
|
||||
bool controlSequenceKnown = false;
|
||||
uint64_t nextCaptureSequence = 0;
|
||||
bool captureSequenceKnown = false;
|
||||
bool captureDiscontinuity = false;
|
||||
|
||||
uint64_t announcedPlayback = 0;
|
||||
uint64_t announcedCapture = 0;
|
||||
uint64_t stateRevision = 0;
|
||||
uint64_t pumpRevision = 0;
|
||||
PumpStage pumpStage = PumpStage::IDLE;
|
||||
KVMFRAudioStatus pendingStatus = {};
|
||||
KVMFRAudioStateBarrier barrier = {};
|
||||
uint64_t barrierGeneration = 0;
|
||||
KVMFRAudioStreamResponse response = {};
|
||||
};
|
||||
|
||||
CLGMPHost& m_host;
|
||||
CLGMPControl& m_control;
|
||||
|
||||
PLGMPHostStream m_hostToClientStream = nullptr;
|
||||
PLGMPHostStream m_clientToHostStream = nullptr;
|
||||
KVMFRStreamDescriptor m_hostToClientDescriptor = {};
|
||||
KVMFRStreamDescriptor m_clientToHostDescriptor = {};
|
||||
PLGMPHostSPMC m_playbackStream = nullptr;
|
||||
KVMFRSPMCDescriptor m_playbackDescriptor = {};
|
||||
PLGMPHostStream m_captureStream = nullptr;
|
||||
KVMFRStreamDescriptor m_captureDescriptor = {};
|
||||
Subscriber m_subscribers[LGMP_MAX_CLIENTS];
|
||||
ResponseMemory m_responseMemory[RESPONSE_COUNT];
|
||||
PendingResponse m_pendingResponse[LGMP_MAX_CLIENTS];
|
||||
ClaimRecord m_claimRecord[CLAIM_HISTORY_COUNT];
|
||||
@@ -113,41 +187,29 @@ private:
|
||||
bool m_inboundStopping = false;
|
||||
IAudioTarget * m_target = nullptr;
|
||||
bool m_initialized = false;
|
||||
bool m_claimResponderReady = false;
|
||||
bool m_started = false;
|
||||
bool m_failed = false;
|
||||
bool m_failureNotified = false;
|
||||
|
||||
OwnerState m_ownerState = OwnerState::NONE;
|
||||
uint32_t m_ownerClientID = 0;
|
||||
uint32_t m_ownerGeneration = 0;
|
||||
uint32_t m_clientCapabilities = 0;
|
||||
uint64_t m_ownerToken = 0;
|
||||
uint64_t m_ownerSerial = 0;
|
||||
uint64_t m_ownerDeadline = 0;
|
||||
uint64_t m_releaseGeneration = 0;
|
||||
bool m_responseAdmitted = false;
|
||||
bool m_clientReady = false;
|
||||
bool m_activationIssued = false;
|
||||
bool m_deactivationIssued = false;
|
||||
KVMFRAudioStreamResponse m_ownerResponse = {};
|
||||
uint32_t m_feedbackAuthority = INVALID_SLOT;
|
||||
uint32_t m_captureOwner = INVALID_SLOT;
|
||||
uint32_t m_captureBinding = INVALID_SLOT;
|
||||
uint32_t m_nextBindingEpoch = 0;
|
||||
uint32_t m_nextRoleEpoch = 0;
|
||||
uint64_t m_nextSubscriberSerial = 0;
|
||||
bool m_activationCommanded = false;
|
||||
|
||||
AudioEndpointStatus m_state;
|
||||
bool m_stateKnown = false;
|
||||
bool m_statusDirty = false;
|
||||
uint64_t m_announcedPlayback = 0;
|
||||
uint64_t m_playbackRunGeneration = 0;
|
||||
uint64_t m_announcedCapture = 0;
|
||||
uint64_t m_hostSequence = 0;
|
||||
uint64_t m_nextClientSequence = 0;
|
||||
bool m_clientSequenceKnown = false;
|
||||
bool m_captureDiscontinuity = false;
|
||||
uint64_t m_previousPlaybackGeneration = 0;
|
||||
bool m_captureCallbackInFlight = false;
|
||||
bool m_playbackBlocked = false;
|
||||
bool m_playbackReadyPending = false;
|
||||
bool m_playbackCreditValid = false;
|
||||
LGMPStreamBuffer m_playbackCredit = {};
|
||||
AudioPacket m_blockedPlayback = {};
|
||||
size_t m_blockedPlaybackSize = 0;
|
||||
uint64_t m_authorityBlockedSince = 0;
|
||||
uint64_t m_captureReadySerial = 0;
|
||||
uint64_t m_inboundActivitySerial = 0;
|
||||
PendingCapture m_pendingCapture;
|
||||
@@ -158,9 +220,11 @@ private:
|
||||
void StopInboundWorker();
|
||||
void InboundThread();
|
||||
static DWORD CALLBACK InboundThreadProc(void * context);
|
||||
bool BindStreams(uint32_t clientID);
|
||||
bool TryUnbindStreams();
|
||||
void ForceUnbindStreams();
|
||||
bool BindSubscriber(uint32_t index, uint32_t clientID,
|
||||
const KVMFRAudioClaim& claim);
|
||||
bool TryUnbindSubscriber(uint32_t index);
|
||||
void ForceUnbindSubscriber(uint32_t index);
|
||||
void ResetSubscriber(uint32_t index);
|
||||
|
||||
static bool SameFormat(const AudioFormat& left,
|
||||
const AudioFormat& right);
|
||||
@@ -171,6 +235,8 @@ private:
|
||||
static KVMFRAudioStreamState WireState(AudioStreamState state);
|
||||
static KVMFRStreamDescriptor WireDescriptor(
|
||||
const LGMPStreamDescriptor& descriptor);
|
||||
static KVMFRSPMCDescriptor WireDescriptor(
|
||||
const LGMPSPMCDescriptor& descriptor);
|
||||
|
||||
bool ValidateClaim(const KVMFRAudioClaim& claim) const;
|
||||
bool ResponsePending(uint32_t clientID,
|
||||
@@ -180,58 +246,74 @@ private:
|
||||
void CleanupResponseMemory();
|
||||
void PumpResponses();
|
||||
|
||||
bool OwnerSubscribed() const;
|
||||
void RenewLease();
|
||||
void BeginRelease(const char * reason);
|
||||
uint32_t FindSubscriber(uint32_t clientID) const;
|
||||
uint32_t FindFreeSubscriber() const;
|
||||
bool SubscriberPresent(uint32_t clientID,
|
||||
const uint32_t clients[LGMP_MAX_CLIENTS], unsigned count) const;
|
||||
void RenewLease(Subscriber& subscriber);
|
||||
void BeginRelease(uint32_t index, const char * reason,
|
||||
bool force = false);
|
||||
void MaintainSubscribers(uint64_t now);
|
||||
bool HasBoundSubscribers() const;
|
||||
bool HasReadyPlayback() const;
|
||||
bool PlaybackEligible(const Subscriber& subscriber) const;
|
||||
bool FeedbackEligible(const Subscriber& subscriber) const;
|
||||
bool CaptureEligible(const Subscriber& subscriber) const;
|
||||
void RefreshGranted(uint32_t index, bool bumpRole);
|
||||
void AssignFeedbackAuthority(uint32_t index);
|
||||
void RevokeFeedbackAuthority(const char * reason);
|
||||
void PromoteFeedbackAuthority(uint64_t playbackGeneration);
|
||||
void AssignCaptureOwner();
|
||||
void MaintainCaptureBinding();
|
||||
void MarkStateDirty(uint32_t index);
|
||||
void MarkAllStateDirty();
|
||||
void ReleasePendingCapture();
|
||||
void CancelPlaybackCredit();
|
||||
void ClearBlockedPlayback(bool notify);
|
||||
bool RememberBlockedPlayback(const AudioPacket& packet, size_t size);
|
||||
void MaintainOwner(uint64_t now);
|
||||
void ResetOwner();
|
||||
|
||||
KVMFRAudioStatus WireStatus() const;
|
||||
bool CommitMessage(LGMPStreamBuffer& buffer,
|
||||
KVMFRAudioStatus WireStatus(uint32_t index) const;
|
||||
bool WriteControlMessage(uint32_t index,
|
||||
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);
|
||||
bool 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);
|
||||
bool PlaybackCurrent(const KVMFRAudioStatus& status) const;
|
||||
bool CaptureCurrent(const KVMFRAudioStatus& status) const;
|
||||
void BeginStatePump(uint32_t index);
|
||||
void PumpSubscriberState(uint32_t index);
|
||||
void PumpState();
|
||||
void CheckPlaybackCredit();
|
||||
void CheckPlaybackBackpressure(uint64_t now);
|
||||
|
||||
bool ParseInbound(const LGMPStreamBuffer& buffer,
|
||||
KVMFRAudioMessage& message) const;
|
||||
bool PendingCaptureCurrent() const;
|
||||
bool PendingCaptureCurrent(uint32_t index) const;
|
||||
bool PrepareCapture(const KVMFRAudioMessage& message,
|
||||
const LGMPStreamBuffer& buffer);
|
||||
const LGMPStreamBuffer& buffer, uint32_t index);
|
||||
void DrainControl(uint32_t index);
|
||||
void DrainCapture();
|
||||
void DrainInbound();
|
||||
void ProcessActivation();
|
||||
void NotifyPlaybackReady();
|
||||
void NotifyFailure();
|
||||
void Fail(const char * operation, LGMP_STATUS status);
|
||||
|
||||
void InitializeOwner(uint32_t clientID,
|
||||
const KVMFRAudioClaim& claim);
|
||||
void HandleClaim(uint32_t clientID,
|
||||
const KVMFRAudioClaim& claim);
|
||||
bool Initialize();
|
||||
bool CanRespondToClaims();
|
||||
void Process();
|
||||
void TransportStop();
|
||||
|
||||
friend class CLGMPTransport;
|
||||
|
||||
public:
|
||||
CLGMPAudioTransport(CLGMPHost& host, CLGMPControl& control) :
|
||||
CLGMPAudio(CLGMPHost& host, CLGMPControl& control) :
|
||||
m_host(host), m_control(control) {}
|
||||
~CLGMPAudioTransport() override;
|
||||
~CLGMPAudio() override;
|
||||
|
||||
CLGMPAudioTransport(const CLGMPAudioTransport&) = delete;
|
||||
CLGMPAudioTransport& operator=(
|
||||
const CLGMPAudioTransport&) = delete;
|
||||
CLGMPAudio(const CLGMPAudio&) = delete;
|
||||
CLGMPAudio& operator=(
|
||||
const CLGMPAudio&) = delete;
|
||||
|
||||
bool Start(IAudioTarget& target) override;
|
||||
void Stop() override;
|
||||
|
||||
@@ -34,13 +34,13 @@
|
||||
#include <memory>
|
||||
|
||||
class CLGMPTransport;
|
||||
class CLGMPAudioTransport;
|
||||
class CLGMPAudio;
|
||||
|
||||
class CLGMPControl final : public IControlSink
|
||||
{
|
||||
private:
|
||||
friend class CLGMPTransport;
|
||||
friend class CLGMPAudioTransport;
|
||||
friend class CLGMPAudio;
|
||||
|
||||
static constexpr int POINTER_SHAPE_BUFFERS = 3;
|
||||
static constexpr int COLOR_TRANSFORM_BUFFERS = 3;
|
||||
|
||||
@@ -157,6 +157,12 @@ LGMP_STATUS CLGMPHost::CreateStream(
|
||||
return lgmpHostStreamNew(m_host, config, stream);
|
||||
}
|
||||
|
||||
LGMP_STATUS CLGMPHost::CreateSPMC(
|
||||
const struct LGMPSPMCConfig& config, PLGMPHostSPMC * stream)
|
||||
{
|
||||
return lgmpHostSPMCNew(m_host, config, stream);
|
||||
}
|
||||
|
||||
LGMP_STATUS CLGMPHost::Allocate(uint32_t size, PLGMPMemory * memory)
|
||||
{
|
||||
return lgmpHostMemAlloc(m_host, size, memory);
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
extern "C" {
|
||||
#include "lgmp/host.h"
|
||||
#include "lgmp/spmc.h"
|
||||
#include "lgmp/stream.h"
|
||||
}
|
||||
|
||||
@@ -57,6 +58,8 @@ public:
|
||||
PLGMPHostQueue * queue);
|
||||
LGMP_STATUS CreateStream(const struct LGMPStreamConfig& config,
|
||||
PLGMPHostStream * stream);
|
||||
LGMP_STATUS CreateSPMC(const struct LGMPSPMCConfig& config,
|
||||
PLGMPHostSPMC * stream);
|
||||
LGMP_STATUS Allocate(uint32_t size, PLGMPMemory * memory);
|
||||
LGMP_STATUS AllocateAligned(uint32_t size, uint32_t alignment,
|
||||
PLGMPMemory * memory);
|
||||
|
||||
@@ -102,7 +102,11 @@ bool CLGMPTransport::Initialize()
|
||||
return false;
|
||||
|
||||
if (!m_audio.Initialize())
|
||||
{
|
||||
if (!m_audio.CanRespondToClaims())
|
||||
return false;
|
||||
DEBUG_WARN("LGMP audio transport is unavailable");
|
||||
}
|
||||
|
||||
m_frames.SealMemoryLayout();
|
||||
return true;
|
||||
|
||||
@@ -40,7 +40,7 @@ private:
|
||||
CIVSHMEM m_ivshmem;
|
||||
CLGMPHost m_host;
|
||||
CLGMPControl m_control;
|
||||
CLGMPAudioTransport m_audio;
|
||||
CLGMPAudio m_audio;
|
||||
CLGMPFrameTransport m_frames;
|
||||
CLGMPInputTransport m_input;
|
||||
CLGMPClipboardTransport m_clipboard;
|
||||
|
||||
Submodule repos/LGProtocol updated: 62b52724b6...c9659b314a
Reference in New Issue
Block a user