Files
LookingGlass/client/include/interface/audio.h
Geoffrey McRae 0b0f227c08 [client/idd] network: discard stale audio after stalls
Keep PCM and its clock sample in cancellable datagrams so recovery
cannot replay audio buffered before a Wi-Fi stall.

Drop blocked real-time packets with an explicit discontinuity and make
a newer reconnect supersede stale audio authorities.
2026-11-06 14:40:30 +11:00

196 lines
7.3 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
*/
#ifndef _H_LG_CLIENT_AUDIO_INTERFACE_
#define _H_LG_CLIENT_AUDIO_INTERFACE_
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#define LG_AUDIO_MAX_CHANNELS 32
typedef enum LG_AudioSampleFormat
{
LG_AUDIO_FMT_U8,
LG_AUDIO_FMT_S16_LE,
/* Signed 24-bit little-endian samples packed into three bytes. */
LG_AUDIO_FMT_S24_LE,
LG_AUDIO_FMT_S32_LE,
/* IEEE 754 little-endian floating-point samples. */
LG_AUDIO_FMT_F32_LE,
LG_AUDIO_FMT_F64_LE,
/* Native-endian floating point for client-side processing. */
LG_AUDIO_FMT_F32_NE,
}
LG_AudioSampleFormat;
typedef enum LG_AudioChannel
{
LG_AUDIO_CH_UNKNOWN,
LG_AUDIO_CH_MONO,
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,
}
LG_AudioChannel;
typedef struct LG_AudioFormat
{
LG_AudioSampleFormat sampleFormat;
uint32_t sampleRate;
uint8_t channelCount;
/* Samples are interleaved in this order. Unknown positions are explicit. */
LG_AudioChannel channels[LG_AUDIO_MAX_CHANNELS];
}
LG_AudioFormat;
typedef struct LG_AudioClock
{
/* Frame position and time describe the same point on the stream timeline.
* Time is monotonic nanoseconds in the publisher's clock domain; consumers
* must use differences unless they know that they share that domain. */
uint64_t position;
int64_t time;
double rate; /* measured frames per second, or zero when unavailable */
bool stable;
bool discontinuity;
}
LG_AudioClock;
typedef struct LG_AudioStatus
{
bool available;
/* Changes whenever the provider endpoint is replaced or restarted. */
uint32_t generation;
}
LG_AudioStatus;
typedef void (*LG_AudioStatusFn)(void * opaque,
const LG_AudioStatus * status);
typedef struct LG_AudioEventOps
{
/* Format and clock pointers are borrowed for the duration of each call.
* A NULL source clock indicates that the provider has no usable clock.
* Providers must serialize event delivery within each stream direction.
* Playback and recording events may be delivered concurrently. Stream
* generations are nonzero and uniquely identify each stream instance. */
void (*playbackStart)(void * opaque, uint32_t generation,
const LG_AudioFormat * format, const LG_AudioClock * sourceClock);
void (*playbackStop)(void * opaque, uint32_t generation);
void (*playbackVolume)(void * opaque, uint32_t generation,
uint8_t channels, const uint16_t volume[]);
void (*playbackMute)(void * opaque, uint32_t generation, bool mute);
/* Sample data is borrowed for the duration of the call. When supplied, the
* source clock position and time identify the first frame in this packet.
* Discontinuity remains valid when no source clock is available. */
void (*playbackData)(void * opaque, uint32_t generation,
const void * data, size_t frames,
const LG_AudioClock * sourceClock, bool discontinuity);
/* May repeat with the same generation to update an active stream format. */
void (*recordStart)(void * opaque, uint32_t generation,
const LG_AudioFormat * format);
void (*recordStop)(void * opaque, uint32_t generation);
void (*recordVolume)(void * opaque, uint32_t generation,
uint8_t channels, const uint16_t volume[]);
void (*recordMute)(void * opaque, uint32_t generation, bool mute);
}
LG_AudioEventOps;
typedef enum LG_AudioPlaybackFlag
{
/* Apply audio:latencyOffset when local playback rate control is active.
* This preserves the legacy safety margin required by classic SPICE. */
LG_AUDIO_PLAYBACK_LEGACY_LATENCY_OFFSET = 1U << 0,
}
LG_AudioPlaybackFlag;
typedef struct LG_AudioOps
{
const char * name;
/* A zero arrival reserve selects the client's default. Providers with a
* tighter, measured delivery bound may request a smaller value. Runtime
* delivery jitter is learned and added independently. */
uint32_t playbackFlags;
uint32_t playbackArrivalReserveUs;
/* Registration must synchronously report the current status after releasing
* any backend locks. Passing NULL unregisters the listener. Providers with
* no status listener are assumed to remain available while their transport
* session is connected. Status callbacks must not be invoked synchronously
* from event delivery or from another operation in this interface. Passing
* NULL must synchronously quiesce any in-flight status callback. */
void (*setStatusListener)(void * opaque, LG_AudioStatusFn callback,
void * callbackOpaque);
/* Attach begins event delivery and replays any active streams. Detach must
* synchronously quiesce event callbacks before returning. The owner must
* stop event delivery before dropping an endpoint that can no longer be
* detached. */
bool (*attach)(void * opaque, const LG_AudioEventOps * events,
void * eventOpaque);
void (*detach)(void * opaque);
/* Send microphone frames to the guest. Data is borrowed for the duration of
* the call and uses the format supplied with the matching recordStart. The
* source clock, when present, identifies the first frame in the buffer. */
bool (*recordData)(void * opaque, uint32_t generation,
const void * data, size_t frames, const LG_AudioClock * sourceClock);
/* Optional active synchronization feedback. A provider implementing this
* owns playback rate control, so the client preserves its frame rate and
* does not activate a local resampler. This is called outside the realtime
* audio callback with the measured playback device clock. Its position uses
* the device's independent output-frame timeline and its time includes the
* backend's estimated presentation latency. targetRate is the source frame
* rate requested by the client's buffer controller. Return false while
* active rate feedback is unavailable; sustained failure makes the client
* restart playback with local rate control. */
bool (*clockFeedback)(void * opaque, uint32_t generation,
const LG_AudioClock * playbackClock, double targetRate);
/* Optional nonblocking capability check used before selecting provider
* rate control for a new stream. If omitted, clockFeedback is assumed to
* be available. */
bool (*clockFeedbackAvailable)(void * opaque);
}
LG_AudioOps;
#endif