/**
 * 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 "usb_audio.h"

#include "common/debug.h"
#include "common/event.h"
#include "common/ringbuffer.h"
#include "common/time.h"

#include <usbredirparser.h>

#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
#include <stdatomic.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

enum
{
  USB_AUDIO_VENDOR_ID                 = 0x043e,
  USB_AUDIO_PRODUCT_ID                = 0x0001,
  USB_AUDIO_DEVICE_VERSION            = 0x010d,
  USB_AUDIO_CONFIGURATION             = 1,
  USB_AUDIO_PLAYBACK_CONTROL_IFACE    = 0,
  USB_AUDIO_PLAYBACK_IFACE            = 1,
  USB_AUDIO_RECORD_CONTROL_IFACE      = 2,
  USB_AUDIO_RECORD_IFACE              = 3,
  USB_AUDIO_PLAYBACK_CLOCK_ID         = 1,
  USB_AUDIO_RECORD_CLOCK_ID           = 1,
  USB_AUDIO_PLAYBACK_DATA_ENDPOINT    = 0x01,
  USB_AUDIO_FEEDBACK_ENDPOINT         = 0x81,
  USB_AUDIO_RECORD_DATA_ENDPOINT      = 0x82,
  USB_AUDIO_PLAYBACK_DATA_EP_INDEX    = 1,
  USB_AUDIO_FEEDBACK_EP_INDEX         = 17,
  USB_AUDIO_RECORD_DATA_EP_INDEX      = 18,
  USB_AUDIO_PLAYBACK_DATA_INTERVAL    = 1,
  USB_AUDIO_FEEDBACK_INTERVAL         = 8,
  USB_AUDIO_RECORD_DATA_INTERVAL      = 4,
  USB_AUDIO_FEEDBACK_BINTERVAL        = 4,
  USB_AUDIO_RECORD_DATA_BINTERVAL     = 3,
  USB_AUDIO_FEEDBACK_SIZE             = 4,
  USB_AUDIO_FEEDBACK_MAX_QUEUE        = 512,
  USB_AUDIO_SAMPLE_SIZE               = 3,
  USB_AUDIO_MAX_SAMPLE_RATE           = 192000,
  USB_AUDIO_PLAYBACK_MAX_CHANNELS     = 8,
  USB_AUDIO_PLAYBACK_HS_PACKET_FRAMES = 25,
  USB_AUDIO_PLAYBACK_BATCH_PACKETS    = USB_AUDIO_FEEDBACK_INTERVAL,
  USB_AUDIO_PLAYBACK_BATCH_FRAMES     =
    USB_AUDIO_PLAYBACK_HS_PACKET_FRAMES * USB_AUDIO_PLAYBACK_BATCH_PACKETS,
  USB_AUDIO_PLAYBACK_BATCH_SIZE       =
    USB_AUDIO_PLAYBACK_BATCH_FRAMES *
      USB_AUDIO_PLAYBACK_MAX_CHANNELS * USB_AUDIO_SAMPLE_SIZE,
  USB_AUDIO_FS_PACKET_FRAMES          = 97,
  USB_AUDIO_RECORD_PACKET_FRAMES      = 97,
  USB_AUDIO_RECORD_PACKETS_PER_SECOND = 2000,
  USB_AUDIO_RECORD_PACKET_INTERVAL_NS = 500000,
  USB_AUDIO_FEEDBACK_PERIOD_NS        = 1000000,
  USB_AUDIO_RECORD_RATE_SCALE         = 65536,
  USB_AUDIO_RECORD_RATE_SETTLE_NS     = 2000000000,
  USB_AUDIO_RECORD_RATE_GAP_NS        = 100000000,
  USB_AUDIO_RECORD_RATE_DENOMINATOR   =
    USB_AUDIO_RECORD_PACKETS_PER_SECOND * USB_AUDIO_RECORD_RATE_SCALE,
  USB_AUDIO_RECORD_MAX_BATCH          = 128,
  USB_AUDIO_RECORD_MAX_SAFETY_PACKETS =
    USB_AUDIO_RECORD_PACKETS_PER_SECOND / 10,
  /* Hold the complete QEMU ISO-IN safety window in addition to the normal
   * 20 ms queue target at the maximum supported sample rate. */
  USB_AUDIO_RECORD_QUEUE_FRAMES       =
    USB_AUDIO_RECORD_MAX_SAFETY_PACKETS *
      USB_AUDIO_RECORD_PACKET_FRAMES +
    USB_AUDIO_MAX_SAMPLE_RATE / 50,
  USB_AUDIO_RECORD_FRAME_SIZE         =
    2 * USB_AUDIO_SAMPLE_SIZE,
  USB_AUDIO_RECORD_PACKET_SIZE        =
    USB_AUDIO_RECORD_PACKET_FRAMES * USB_AUDIO_RECORD_FRAME_SIZE,
  USB_AUDIO_CONFIGURATION_DESC_SIZE   = 9,
  USB_AUDIO_FUNCTION_CONTROL_SIZE     = 63,
  USB_AUDIO_PLAYBACK_STREAM_DESC_SIZE = 53,
  USB_AUDIO_RECORD_STREAM_DESC_SIZE   = 46,
};

#define USB_AUDIO_RECORD_DEBUG_INTERVAL_NS   UINT64_C(5000000000)
#define USB_AUDIO_PLAYBACK_DEBUG_INTERVAL_NS UINT64_C(5000000000)

enum
{
  USB_AUDIO_LAYOUT_STEREO  = 0x00000003,
  USB_AUDIO_LAYOUT_QUAD    = 0x00000033,
  USB_AUDIO_LAYOUT_5POINT1 = 0x0000003f,
  USB_AUDIO_LAYOUT_7POINT1 = 0x0000063f,
};

#define USB_AUDIO_LAYOUTS(X) \
  X(1, 8, USB_AUDIO_LAYOUT_7POINT1) \
  X(2, 6, USB_AUDIO_LAYOUT_5POINT1) \
  X(3, 4, USB_AUDIO_LAYOUT_QUAD   ) \
  X(4, 2, USB_AUDIO_LAYOUT_STEREO )

#define USB_AUDIO_COUNT_LAYOUT(alt, channels, mask) + 1
enum
{
  USB_AUDIO_LAYOUT_COUNT =
    0 USB_AUDIO_LAYOUTS(USB_AUDIO_COUNT_LAYOUT),
  USB_AUDIO_RECORD_FUNCTION_OFFSET =
    USB_AUDIO_CONFIGURATION_DESC_SIZE +
    USB_AUDIO_FUNCTION_CONTROL_SIZE + 9 +
    USB_AUDIO_LAYOUT_COUNT * USB_AUDIO_PLAYBACK_STREAM_DESC_SIZE,
  USB_AUDIO_CONFIGURATION_SIZE =
    USB_AUDIO_RECORD_FUNCTION_OFFSET +
    USB_AUDIO_FUNCTION_CONTROL_SIZE +
    9 + USB_AUDIO_RECORD_STREAM_DESC_SIZE,
  USB_AUDIO_OTHER_SPEED_SIZE =
    USB_AUDIO_CONFIGURATION_DESC_SIZE +
    USB_AUDIO_FUNCTION_CONTROL_SIZE +
    9 + USB_AUDIO_PLAYBACK_STREAM_DESC_SIZE +
    USB_AUDIO_FUNCTION_CONTROL_SIZE +
    9 + USB_AUDIO_RECORD_STREAM_DESC_SIZE,
};
#undef USB_AUDIO_COUNT_LAYOUT

#define USB_AUDIO_PACKET_SIZE(frames, channels) \
  ((frames) * (channels) * USB_AUDIO_SAMPLE_SIZE)

typedef struct USBAudioLayout
{
  uint32_t channelMask;
  uint8_t  channelCount;
}
USBAudioLayout;

#define USB_AUDIO_LAYOUT_ENTRY(alt, channels, mask) \
  [alt - 1] = { .channelMask = mask, .channelCount = channels },
static const USBAudioLayout l_channelLayouts[] =
{
  USB_AUDIO_LAYOUTS(USB_AUDIO_LAYOUT_ENTRY)
};
#undef USB_AUDIO_LAYOUT_ENTRY

enum
{
  USB_REQUEST_GET_STATUS     = 0x00,
  USB_REQUEST_GET_DESCRIPTOR = 0x06,
  USB_REQUEST_CUR            = 0x01,
  USB_REQUEST_RANGE          = 0x02,
};

enum
{
  USB_REQUEST_TYPE_IN_STANDARD_DEVICE    = 0x80,
  USB_REQUEST_TYPE_IN_STANDARD_INTERFACE = 0x81,
  USB_REQUEST_TYPE_IN_STANDARD_ENDPOINT  = 0x82,
  USB_REQUEST_TYPE_OUT_CLASS_INTERFACE   = 0x21,
  USB_REQUEST_TYPE_IN_CLASS_INTERFACE    = 0xa1,
};

enum
{
  USB_DESCRIPTOR_DEVICE           = 0x01,
  USB_DESCRIPTOR_CONFIGURATION    = 0x02,
  USB_DESCRIPTOR_STRING           = 0x03,
  USB_DESCRIPTOR_DEVICE_QUALIFIER = 0x06,
  USB_DESCRIPTOR_OTHER_SPEED      = 0x07,
};

enum
{
  USB_AUDIO_CONTROL_FREQUENCY = 0x01,
  USB_AUDIO_CONTROL_VALIDITY  = 0x02,
};

static const uint8_t l_deviceDescriptor[] =
{
  0x12, 0x01, 0x00, 0x02, 0xef, 0x02, 0x01, 0x40,
  0x3e, 0x04, 0x01, 0x00,
  USB_AUDIO_DEVICE_VERSION       & 0xff,
  USB_AUDIO_DEVICE_VERSION >> 8  & 0xff,
  0x01, 0x02,
  0x03, 0x01,
};

#define USB_AUDIO_PLAYBACK_STREAM_DESCRIPTOR(alt, channels, mask, \
    packetFrames, feedbackSize, feedbackInterval) \
  0x09, 0x04, USB_AUDIO_PLAYBACK_IFACE, alt, \
    0x02, 0x01, 0x02, 0x20, 0x00, \
  0x10, 0x24, 0x01, 0x02, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, \
    channels, \
    (mask)       & 0xff, \
    (mask) >>  8 & 0xff, \
    (mask) >> 16 & 0xff, \
    (mask) >> 24 & 0xff, \
    0x00, \
  0x06, 0x24, 0x02, 0x01, 0x03, 0x18, \
  0x07, 0x05, USB_AUDIO_PLAYBACK_DATA_ENDPOINT, 0x05, \
    USB_AUDIO_PACKET_SIZE(packetFrames, channels)      & 0xff, \
    USB_AUDIO_PACKET_SIZE(packetFrames, channels) >> 8 & 0xff, \
    USB_AUDIO_PLAYBACK_DATA_INTERVAL, \
  0x08, 0x25, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, \
  0x07, 0x05, USB_AUDIO_FEEDBACK_ENDPOINT, 0x11, \
    feedbackSize, 0x00, feedbackInterval,

#define USB_AUDIO_PLAYBACK_HS_STREAM_DESCRIPTOR(alt, channels, mask) \
  USB_AUDIO_PLAYBACK_STREAM_DESCRIPTOR( \
      alt, channels, mask, USB_AUDIO_PLAYBACK_HS_PACKET_FRAMES, \
      USB_AUDIO_FEEDBACK_SIZE, USB_AUDIO_FEEDBACK_BINTERVAL)

#define USB_AUDIO_RECORD_STREAM_DESCRIPTOR(packetFrames, interval) \
  0x09, 0x04, USB_AUDIO_RECORD_IFACE, 0x01, \
    0x01, 0x01, 0x02, 0x20, 0x00, \
  0x10, 0x24, 0x01, 0x03, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, \
    0x02, 0x03, 0x00, 0x00, 0x00, 0x00, \
  0x06, 0x24, 0x02, 0x01, 0x03, 0x18, \
  0x07, 0x05, USB_AUDIO_RECORD_DATA_ENDPOINT, 0x05, \
    USB_AUDIO_PACKET_SIZE(packetFrames, 2)      & 0xff, \
    USB_AUDIO_PACKET_SIZE(packetFrames, 2) >> 8 & 0xff, \
    interval, \
  0x08, 0x25, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,

static const uint8_t l_configurationDescriptor[] =
{
  /* Configuration */
  0x09, 0x02,
  USB_AUDIO_CONFIGURATION_SIZE       & 0xff,
  USB_AUDIO_CONFIGURATION_SIZE >> 8  & 0xff,
  0x04, 0x01, 0x00, 0x80, 0x32,

  /* Playback audio function */
  0x08, 0x0b, USB_AUDIO_PLAYBACK_CONTROL_IFACE, 0x02,
    0x01, 0x00, 0x20, 0x02,

  0x09, 0x04, USB_AUDIO_PLAYBACK_CONTROL_IFACE, 0x00,
    0x00, 0x01, 0x01, 0x20, 0x00,
  0x09, 0x24, 0x01, 0x00, 0x02, 0x02, 0x2e, 0x00, 0x00,
  0x08, 0x24, 0x0a, USB_AUDIO_PLAYBACK_CLOCK_ID,
    0x03, 0x07, 0x00, 0x00,
  0x11, 0x24, 0x02, 0x02, 0x01, 0x01, 0x00, 0x01, 0x08,
  0x3f, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x0c, 0x24, 0x03, 0x03, 0x01, 0x03, 0x00, 0x02, 0x01,
  0x00, 0x00, 0x00,

  /* Playback interface, idle alternate setting */
  0x09, 0x04, USB_AUDIO_PLAYBACK_IFACE, 0x00,
    0x00, 0x01, 0x02, 0x20, 0x00,

  /* Publish the maximum topology first for the Windows endpoint model. */
  USB_AUDIO_LAYOUTS(USB_AUDIO_PLAYBACK_HS_STREAM_DESCRIPTOR)

  /* Recording audio function */
  0x08, 0x0b, USB_AUDIO_RECORD_CONTROL_IFACE, 0x02,
    0x01, 0x00, 0x20, 0x04,

  0x09, 0x04, USB_AUDIO_RECORD_CONTROL_IFACE, 0x00,
    0x00, 0x01, 0x01, 0x20, 0x00,
  0x09, 0x24, 0x01, 0x00, 0x02, 0x03, 0x2e, 0x00, 0x00,
  0x08, 0x24, 0x0a, USB_AUDIO_RECORD_CLOCK_ID,
    0x03, 0x07, 0x00, 0x00,
  0x11, 0x24, 0x02, 0x02, 0x01, 0x02, 0x00, 0x01, 0x02,
    0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
  0x0c, 0x24, 0x03, 0x03, 0x01, 0x01, 0x00, 0x02, 0x01,
    0x00, 0x00, 0x00,

  /* Recording interface, idle alternate setting */
  0x09, 0x04, USB_AUDIO_RECORD_IFACE, 0x00,
    0x00, 0x01, 0x02, 0x20, 0x00,
  USB_AUDIO_RECORD_STREAM_DESCRIPTOR(
      USB_AUDIO_RECORD_PACKET_FRAMES,
      USB_AUDIO_RECORD_DATA_BINTERVAL)
};
#undef USB_AUDIO_PLAYBACK_HS_STREAM_DESCRIPTOR
#undef USB_AUDIO_LAYOUTS

static const uint8_t l_fullSpeedPlaybackDescriptor[] =
{
  0x09, 0x04, USB_AUDIO_PLAYBACK_IFACE, 0x00,
    0x00, 0x01, 0x02, 0x20, 0x00,
  USB_AUDIO_PLAYBACK_STREAM_DESCRIPTOR(1, 2, USB_AUDIO_LAYOUT_STEREO,
      USB_AUDIO_FS_PACKET_FRAMES, 3, 1)
};

static const uint8_t l_fullSpeedRecordDescriptor[] =
{
  0x09, 0x04, USB_AUDIO_RECORD_IFACE, 0x00,
    0x00, 0x01, 0x02, 0x20, 0x00,
  USB_AUDIO_RECORD_STREAM_DESCRIPTOR(
      USB_AUDIO_FS_PACKET_FRAMES, 1)
};
#undef USB_AUDIO_RECORD_STREAM_DESCRIPTOR
#undef USB_AUDIO_PLAYBACK_STREAM_DESCRIPTOR
#undef USB_AUDIO_PACKET_SIZE

static const uint8_t l_deviceQualifierDescriptor[] =
{
  0x0a, 0x06, 0x00, 0x02, 0xef, 0x02, 0x01, 0x40, 0x01, 0x00,
};

static const uint32_t l_sampleRates[] =
{
   44100,
   48000,
   88200,
   96000,
  176400,
  USB_AUDIO_MAX_SAMPLE_RATE,
};

static const uint8_t l_clockValid[] = { 0x01 };
static const uint8_t l_zeroStatus[] = { 0x00, 0x00 };

_Static_assert(sizeof(l_deviceDescriptor) == 18,
    "invalid USB device descriptor size");
_Static_assert(sizeof(l_channelLayouts) / sizeof(*l_channelLayouts) ==
    USB_AUDIO_LAYOUT_COUNT, "invalid USB audio layout count");
_Static_assert(sizeof(l_configurationDescriptor) ==
    USB_AUDIO_CONFIGURATION_SIZE,
    "invalid USB configuration descriptor size");
_Static_assert(sizeof(l_fullSpeedPlaybackDescriptor) ==
    9 + USB_AUDIO_PLAYBACK_STREAM_DESC_SIZE,
    "invalid full-speed playback descriptor size");
_Static_assert(sizeof(l_fullSpeedRecordDescriptor) ==
    9 + USB_AUDIO_RECORD_STREAM_DESC_SIZE,
    "invalid full-speed recording descriptor size");
_Static_assert(sizeof(l_deviceQualifierDescriptor) == 10,
    "invalid USB device qualifier descriptor size");
_Static_assert(USB_AUDIO_OTHER_SPEED_SIZE <=
    sizeof(l_configurationDescriptor), "invalid other-speed descriptor size");
_Static_assert(USB_AUDIO_FS_PACKET_FRAMES * 2 * USB_AUDIO_SAMPLE_SIZE <=
    1023, "full-speed stream packet is too large");
_Static_assert(USB_AUDIO_RECORD_PACKET_SIZE <= 1024,
    "high-speed recording packet is too large");

typedef enum
{
  RECORD_RATE_NONE,
  RECORD_RATE_ARRIVAL,
  RECORD_RATE_SOURCE
}
RecordRateMode;

struct LG_USBAudio
{
  const LG_USBAudioEventOps * events;
  void                      * eventOpaque;
  struct usbredirparser     * parser;
  bool                        debug;

  uint8_t                  configuration;
  uint8_t                  playbackAlt;
  uint8_t                  recordAlt;
  uint32_t                 playbackSampleRate;
  atomic_uint              recordSampleRate;
  bool                     playbackStreaming;
  uint8_t                  playbackBatchPackets;
  uint8_t                  playbackBatchFrameSize;
  uint16_t                 playbackBatchFrames;
  uint32_t                 playbackPacketPhase;
  bool                     playbackErrorWarned;
  uint64_t                 playbackConcealedPackets;
  uint64_t                 playbackInvalidPackets;
  uint64_t                 playbackNextDebugTime;
  atomic_bool              feedbackStreaming;
  uint64_t                 feedbackPacketId;
  uint64_t                 feedbackNextPacketTime;
  uint32_t                 feedbackQueueTarget;
  atomic_uint              feedbackValue;
  bool                     outputBackpressured;

  atomic_bool              recordStreaming;
  atomic_bool              recordAccepting;
  atomic_bool              recordOverflowPending;
  atomic_uint              recordGeneration;
  atomic_uint              recordInFlight;
  LGEvent                * recordIdle;
  uint64_t                 recordPacketId;
  uint64_t                 recordPacketPhase;
  uint64_t                 recordNextPacketTime;
  uint32_t                 recordBatchPackets;
  uint32_t                 recordSafetyPackets;
  uint32_t                 recordRefillPackets;
  RingBuffer               recordBuffer;
  atomic_uint_fast64_t     recordRateQ16;
  RecordRateMode           recordRateMode;
  uint64_t                 recordArrivalStartTime;
  uint64_t                 recordArrivalLastTime;
  uint64_t                 recordArrivalFrames;
  uint64_t                 recordSourceStartPosition;
  uint64_t                 recordSourceLastPosition;
  int64_t                  recordSourceStartTime;
  int64_t                  recordSourceLastTime;

  atomic_uint_fast64_t     recordOverflowFrames;
  atomic_uint_fast64_t     recordUnderflowFrames;
  atomic_uint_fast64_t     recordTrimmedFrames;
  atomic_uint_fast64_t     recordLatePackets;
  atomic_uint_fast64_t     recordMaxLateness;
  atomic_uint_fast64_t     recordDiscontinuities;
  uint64_t                 recordNextDebugTime;

  uint8_t                  playbackBatch[USB_AUDIO_PLAYBACK_BATCH_SIZE];
};

static LG_USBAudio * getAudio(void * opaque)
{
  return lgUsbRedir_device(opaque);
}

static uint64_t streamTime(void)
{
  /* PipeWire timestamps use CLOCK_MONOTONIC. Keep USB stream scheduling in
   * that domain instead of introducing MONOTONIC_RAW clock drift. */
  return microtime() * UINT64_C(1000);
}

static void recordCounterMax(
    atomic_uint_fast64_t * counter, uint64_t value)
{
  uint_fast64_t current = atomic_load_explicit(
      counter, memory_order_relaxed);
  while (current < value && !atomic_compare_exchange_weak_explicit(
        counter, &current, value,
        memory_order_relaxed, memory_order_relaxed))
    ;
}

static void recordDisableInput(LG_USBAudio * audio)
{
  /* A producer takes the generation before entering recordInFlight. Changing
   * both values prevents a callback which raced the gate from entering after
   * the consumer has reset the SPSC queue. */
  atomic_store_explicit(
      &audio->recordAccepting, false, memory_order_seq_cst);
  atomic_fetch_add_explicit(
      &audio->recordGeneration, 1, memory_order_seq_cst);
}

static void recordWaitForInput(LG_USBAudio * audio)
{
  while (atomic_load_explicit(
        &audio->recordInFlight, memory_order_seq_cst))
    if (!lgWaitEvent(audio->recordIdle, TIMEOUT_INFINITE))
      DEBUG_FATAL("Failed to wait for USB capture callbacks");
}

static int recordDiscardQueued(LG_USBAudio * audio)
{
  const int queued = ringbuffer_getCount(audio->recordBuffer);
  return queued > 0 ?
    ringbuffer_consume(audio->recordBuffer, NULL, queued) : 0;
}

static uint32_t readLE32(const uint8_t * data)
{
  return
    (uint32_t)data[0]       |
    (uint32_t)data[1] <<  8 |
    (uint32_t)data[2] << 16 |
    (uint32_t)data[3] << 24;
}

static void writeLE16(uint8_t * data, uint16_t value)
{
  data[0] = value;
  data[1] = value >> 8;
}

static void writeLE32(uint8_t * data, uint32_t value)
{
  data[0] = value;
  data[1] = value >> 8;
  data[2] = value >> 16;
  data[3] = value >> 24;
}

static uint32_t encodeFeedbackRate(double sampleRate)
{
  return (uint32_t)(sampleRate * UINT32_C(65536) / 8000.0 + 0.5);
}

static void resetFeedbackRate(LG_USBAudio * audio)
{
  atomic_store_explicit(&audio->feedbackValue,
      encodeFeedbackRate(audio->playbackSampleRate), memory_order_release);
}

static uint32_t nextPlaybackPacketFrames(LG_USBAudio * audio)
{
  const uint64_t phase = audio->playbackPacketPhase +
    atomic_load_explicit(&audio->feedbackValue, memory_order_acquire);
  audio->playbackPacketPhase = phase & UINT32_C(0xffff);
  return (uint32_t)(phase >> 16);
}

static uint32_t nearestSampleRate(uint32_t sampleRate)
{
  uint32_t selected  = l_sampleRates[0];
  uint64_t bestDelta = sampleRate > selected ?
    (uint64_t)sampleRate - selected : (uint64_t)selected - sampleRate;

  for (size_t i = 1;
      i < sizeof(l_sampleRates) / sizeof(*l_sampleRates); ++i)
  {
    const uint32_t candidate = l_sampleRates[i];
    const uint64_t delta     = sampleRate > candidate ?
      (uint64_t)sampleRate - candidate : (uint64_t)candidate - sampleRate;
    if (delta < bestDelta)
    {
      selected  = candidate;
      bestDelta = delta;
    }
  }

  return selected;
}

static const USBAudioLayout * getLayout(uint8_t alt)
{
  if (alt == 0 || alt > USB_AUDIO_LAYOUT_COUNT)
    return NULL;

  return &l_channelLayouts[alt - 1];
}

static uint16_t layoutPacketSize(const USBAudioLayout * layout)
{
  return USB_AUDIO_PLAYBACK_HS_PACKET_FRAMES * layout->channelCount *
    USB_AUDIO_SAMPLE_SIZE;
}

static size_t writeSampleRateRange(uint8_t * buffer)
{
  const size_t count = sizeof(l_sampleRates) / sizeof(*l_sampleRates);
  writeLE16(buffer, (uint16_t)count);

  uint8_t * range = buffer + 2;
  for (size_t i = 0; i < count; ++i, range += 12)
  {
    writeLE32(range     , l_sampleRates[i]);
    writeLE32(range +  4, l_sampleRates[i]);
    writeLE32(range +  8, 0);
  }

  return 2 + count * 12;
}

static void flushPlayback(LG_USBAudio * audio)
{
  const size_t frames = audio->playbackBatchFrames;
  audio->playbackBatchPackets   = 0;
  audio->playbackBatchFrameSize = 0;
  audio->playbackBatchFrames    = 0;

  if (frames && audio->playbackStreaming && audio->events &&
      audio->events->playbackData)
    audio->events->playbackData(
        audio->eventOpaque, audio->playbackBatch, frames);
}

static void queuePlaybackPacket(LG_USBAudio * audio,
    const uint8_t * data, size_t frames, uint8_t frameSize)
{
  if (audio->playbackBatchPackets == USB_AUDIO_PLAYBACK_BATCH_PACKETS ||
      audio->playbackBatchFrames + frames >
        USB_AUDIO_PLAYBACK_BATCH_FRAMES ||
      (audio->playbackBatchFrames &&
       audio->playbackBatchFrameSize != frameSize))
    flushPlayback(audio);

  if (frames)
  {
    uint8_t * destination = audio->playbackBatch +
      (size_t)audio->playbackBatchFrames * frameSize;
    if (data)
      memcpy(destination, data, frames * frameSize);
    else
      memset(destination, 0, frames * frameSize);
    audio->playbackBatchFrameSize = frameSize;
    audio->playbackBatchFrames   += (uint16_t)frames;
  }
  ++audio->playbackBatchPackets;
}

static void stopPlayback(LG_USBAudio * audio)
{
  flushPlayback(audio);
  audio->playbackPacketPhase = 0;
  if (!audio->playbackStreaming)
    return;

  audio->playbackStreaming = false;
  resetFeedbackRate(audio);
  if (audio->events && audio->events->playbackStop)
    audio->events->playbackStop(audio->eventOpaque);
}

static void stopFeedback(LG_USBAudio * audio)
{
  atomic_store_explicit(
      &audio->feedbackStreaming, false, memory_order_release);
  audio->feedbackPacketId       = 0;
  audio->feedbackNextPacketTime = 0;
  audio->feedbackQueueTarget    = 0;
}

static void stopPlaybackStreams(LG_USBAudio * audio)
{
  stopPlayback(audio);
  stopFeedback(audio);
}

static void startPlayback(LG_USBAudio * audio)
{
  const USBAudioLayout * layout = getLayout(audio->playbackAlt);
  if (audio->playbackStreaming || !layout)
    return;

  resetFeedbackRate(audio);
  audio->playbackPacketPhase        = 0;
  audio->playbackErrorWarned        = false;
  audio->playbackConcealedPackets   = 0;
  audio->playbackInvalidPackets     = 0;
  audio->playbackNextDebugTime      = 0;
  audio->playbackStreaming          = true;
  if (audio->events && audio->events->playbackStart)
    audio->events->playbackStart(
        audio->eventOpaque, audio->playbackSampleRate,
        layout->channelMask);
}

static void stopRecord(LG_USBAudio * audio)
{
  if (!atomic_load_explicit(
        &audio->recordStreaming, memory_order_acquire))
    return;

  recordDisableInput(audio);
  atomic_store_explicit(
      &audio->recordStreaming, false, memory_order_release);

  if (audio->events && audio->events->recordStop)
    audio->events->recordStop(audio->eventOpaque);

  recordWaitForInput(audio);
  ringbuffer_reset(audio->recordBuffer);
  atomic_store_explicit(
      &audio->recordOverflowPending, false, memory_order_relaxed);
  audio->recordRefillPackets = 0;
}

static void clearRecordRateWindows(LG_USBAudio * audio)
{
  audio->recordRateMode            = RECORD_RATE_NONE;
  audio->recordArrivalStartTime    = 0;
  audio->recordArrivalLastTime     = 0;
  audio->recordArrivalFrames       = 0;
  audio->recordSourceStartPosition = 0;
  audio->recordSourceLastPosition  = 0;
  audio->recordSourceStartTime     = 0;
  audio->recordSourceLastTime      = 0;
}

static void resetRecordRate(
    LG_USBAudio * audio, uint32_t sampleRate)
{
  atomic_store_explicit(&audio->recordRateQ16,
      (uint64_t)sampleRate * USB_AUDIO_RECORD_RATE_SCALE,
      memory_order_release);
  clearRecordRateWindows(audio);
}

static bool recordRateInRange(
    uint32_t sampleRate, double measured)
{
  return measured >= sampleRate * 0.995 &&
    measured <= sampleRate * 1.005;
}

static void updateMeasuredRecordRate(
    LG_USBAudio * audio, double measured)
{
  const uint64_t measuredQ16 = (uint64_t)(
      measured * USB_AUDIO_RECORD_RATE_SCALE + 0.5);
  const uint64_t currentQ16 = atomic_load_explicit(
      &audio->recordRateQ16, memory_order_relaxed);
  atomic_store_explicit(&audio->recordRateQ16,
      (currentQ16 * 3 + measuredQ16) / 4,
      memory_order_release);
}

static void startRecordArrivalWindow(
    LG_USBAudio * audio, uint64_t now)
{
  clearRecordRateWindows(audio);
  audio->recordRateMode         = RECORD_RATE_ARRIVAL;
  audio->recordArrivalStartTime = now;
  audio->recordArrivalLastTime  = now;
}

static void updateRecordArrivalRate(
    LG_USBAudio * audio, uint32_t sampleRate, size_t frames)
{
  const uint64_t now = streamTime();
  if (audio->recordRateMode != RECORD_RATE_ARRIVAL)
  {
    /* Preserve the current rate while a temporarily absent source clock
     * starts a fresh fallback window. */
    startRecordArrivalWindow(audio, now);
    return;
  }

  if (now < audio->recordArrivalLastTime ||
      now - audio->recordArrivalLastTime > USB_AUDIO_RECORD_RATE_GAP_NS)
  {
    atomic_store_explicit(&audio->recordRateQ16,
        (uint64_t)sampleRate * USB_AUDIO_RECORD_RATE_SCALE,
        memory_order_release);
    startRecordArrivalWindow(audio, now);
    return;
  }

  audio->recordArrivalFrames  += frames;
  audio->recordArrivalLastTime = now;
  const uint64_t elapsed = now - audio->recordArrivalStartTime;
  if (elapsed < USB_AUDIO_RECORD_RATE_SETTLE_NS)
    return;

  const double measured =
    (double)audio->recordArrivalFrames * 1000000000.0 / elapsed;
  if (recordRateInRange(sampleRate, measured))
    updateMeasuredRecordRate(audio, measured);

  audio->recordArrivalStartTime = now;
  audio->recordArrivalFrames    = 0;
}

static void startRecordSourceWindow(
    LG_USBAudio * audio, const LG_AudioClock * sourceClock)
{
  clearRecordRateWindows(audio);
  audio->recordRateMode            = RECORD_RATE_SOURCE;
  audio->recordSourceStartPosition = sourceClock->position;
  audio->recordSourceLastPosition  = sourceClock->position;
  audio->recordSourceStartTime     = sourceClock->time;
  audio->recordSourceLastTime      = sourceClock->time;
}

static bool updateRecordSourceRate(LG_USBAudio * audio,
    uint32_t sampleRate, const LG_AudioClock * sourceClock)
{
  if (sourceClock->discontinuity)
  {
    resetRecordRate(audio, sampleRate);
    return false;
  }

  if (audio->recordRateMode != RECORD_RATE_SOURCE)
  {
    /* Clock stability is not needed to establish the first position/time
     * baseline. */
    startRecordSourceWindow(audio, sourceClock);
    return true;
  }

  if (sourceClock->position <= audio->recordSourceLastPosition ||
      sourceClock->time <= audio->recordSourceLastTime)
  {
    resetRecordRate(audio, sampleRate);
    return false;
  }

  if (!sourceClock->stable)
  {
    /* Keep an unstable clock as the next baseline, but do not let it
     * contribute to the measured device rate. */
    startRecordSourceWindow(audio, sourceClock);
    return true;
  }

  audio->recordSourceLastPosition = sourceClock->position;
  audio->recordSourceLastTime     = sourceClock->time;
  const int64_t elapsed =
    sourceClock->time - audio->recordSourceStartTime;
  if (elapsed < USB_AUDIO_RECORD_RATE_SETTLE_NS)
    return true;

  const double measured =
    (double)(sourceClock->position -
        audio->recordSourceStartPosition) * 1000000000.0 / elapsed;
  if (!recordRateInRange(sampleRate, measured))
  {
    resetRecordRate(audio, sampleRate);
    return false;
  }

  updateMeasuredRecordRate(audio, measured);
  audio->recordSourceStartPosition = sourceClock->position;
  audio->recordSourceStartTime     = sourceClock->time;
  return true;
}

static void resetRecordData(LG_USBAudio * audio)
{
  ringbuffer_reset(audio->recordBuffer);
  resetRecordRate(audio, atomic_load_explicit(
        &audio->recordSampleRate, memory_order_relaxed));
  audio->recordPacketPhase   = 0;
  audio->recordRefillPackets = 0;
  atomic_store_explicit(
      &audio->recordOverflowPending, false, memory_order_relaxed);
}

static void startRecord(LG_USBAudio * audio)
{
  if (audio->recordAlt != 1 || atomic_load_explicit(
        &audio->recordStreaming, memory_order_acquire))
    return;

  recordDisableInput(audio);
  recordWaitForInput(audio);
  resetRecordData(audio);
  audio->recordPacketId = 0;
  atomic_store_explicit(
      &audio->recordStreaming, true, memory_order_release);

  if (audio->events && audio->events->recordStart)
    audio->events->recordStart(audio->eventOpaque,
        atomic_load_explicit(
          &audio->recordSampleRate, memory_order_relaxed),
        USB_AUDIO_LAYOUT_STEREO);

  /* Backend startup may block while the capture graph is configured. Do not
   * turn that setup time into a burst of stale USB packets. */
  recordWaitForInput(audio);
  audio->recordNextPacketTime = streamTime();
  atomic_store_explicit(
      &audio->recordAccepting, true, memory_order_seq_cst);
}

static void reconfigureRecord(
    LG_USBAudio * audio, uint32_t sampleRate)
{
  if (!atomic_load_explicit(
        &audio->recordStreaming, memory_order_acquire))
    return;

  recordDisableInput(audio);
  atomic_store_explicit(
      &audio->recordSampleRate, sampleRate, memory_order_relaxed);
  if (audio->events && audio->events->recordStart)
    audio->events->recordStart(audio->eventOpaque,
        sampleRate, USB_AUDIO_LAYOUT_STEREO);

  /* The provider restart has quiesced the old producer. Drop any frames
   * queued around the format transition and restart the USB sample phase. */
  recordWaitForInput(audio);
  resetRecordData(audio);
  audio->recordNextPacketTime = streamTime();
  atomic_store_explicit(
      &audio->recordAccepting, true, memory_order_seq_cst);
}

static void stopStreams(LG_USBAudio * audio)
{
  stopPlaybackStreams(audio);
  stopRecord(audio);
}

static void setPlaybackSampleRate(
    LG_USBAudio * audio, uint32_t sampleRate)
{
  if (audio->playbackSampleRate == sampleRate)
    return;

  const bool restartPlayback = audio->playbackStreaming;
  stopPlayback(audio);
  audio->playbackSampleRate = sampleRate;
  resetFeedbackRate(audio);
  if (restartPlayback)
    startPlayback(audio);
}

static void setRecordSampleRate(
    LG_USBAudio * audio, uint32_t sampleRate)
{
  if (atomic_load_explicit(
        &audio->recordSampleRate, memory_order_relaxed) == sampleRate)
    return;

  if (atomic_load_explicit(
        &audio->recordStreaming, memory_order_acquire))
    reconfigureRecord(audio, sampleRate);
  else
  {
    atomic_store_explicit(
        &audio->recordSampleRate, sampleRate, memory_order_relaxed);
    atomic_store_explicit(&audio->recordRateQ16,
        (uint64_t)sampleRate * USB_AUDIO_RECORD_RATE_SCALE,
        memory_order_relaxed);
  }
}

static void resetDevice(LG_USBAudio * audio)
{
  stopStreams(audio);
  audio->configuration       = 0;
  audio->playbackAlt         = 0;
  audio->recordAlt           = 0;
  audio->playbackSampleRate  = LG_USB_AUDIO_DEFAULT_SAMPLE_RATE;
  audio->recordBatchPackets  = 1;
  audio->recordSafetyPackets = 1;
  audio->outputBackpressured = false;
  atomic_store_explicit(&audio->recordSampleRate,
      LG_USB_AUDIO_DEFAULT_SAMPLE_RATE, memory_order_relaxed);
  atomic_store_explicit(&audio->recordRateQ16,
      (uint64_t)LG_USB_AUDIO_DEFAULT_SAMPLE_RATE *
        USB_AUDIO_RECORD_RATE_SCALE,
      memory_order_relaxed);
  resetFeedbackRate(audio);
}

static void sendInterfaceInfo(LG_USBAudio * audio)
{
  struct usb_redir_interface_info_header info =
  {
    .interface_count = 4,
    .interface =
    {
      USB_AUDIO_PLAYBACK_CONTROL_IFACE,
      USB_AUDIO_PLAYBACK_IFACE,
      USB_AUDIO_RECORD_CONTROL_IFACE,
      USB_AUDIO_RECORD_IFACE,
    },
    .interface_class    = { 0x01, 0x01, 0x01, 0x01 },
    .interface_subclass = { 0x01, 0x02, 0x01, 0x02 },
    .interface_protocol = { 0x20, 0x20, 0x20, 0x20 },
  };
  usbredirparser_send_interface_info(audio->parser, &info);
}

static void sendEndpointInfo(LG_USBAudio * audio)
{
  struct usb_redir_ep_info_header info = { 0 };
  const USBAudioLayout * layout = getLayout(audio->playbackAlt);
  memset(info.type, usb_redir_type_invalid, sizeof(info.type));

  info.type[0]             = usb_redir_type_control;
  info.type[16]            = usb_redir_type_control;
  info.max_packet_size[0]  = 64;
  info.max_packet_size[16] = 64;

  if (audio->configuration == USB_AUDIO_CONFIGURATION && layout)
  {
    info.type[USB_AUDIO_PLAYBACK_DATA_EP_INDEX] = usb_redir_type_iso;
    info.interval[USB_AUDIO_PLAYBACK_DATA_EP_INDEX] =
      USB_AUDIO_PLAYBACK_DATA_INTERVAL;
    info.interface[USB_AUDIO_PLAYBACK_DATA_EP_INDEX] =
      USB_AUDIO_PLAYBACK_IFACE;
    info.max_packet_size[USB_AUDIO_PLAYBACK_DATA_EP_INDEX] =
      layoutPacketSize(layout);

    info.type[USB_AUDIO_FEEDBACK_EP_INDEX]            = usb_redir_type_iso;
    info.interval[USB_AUDIO_FEEDBACK_EP_INDEX]        =
      USB_AUDIO_FEEDBACK_INTERVAL;
    info.interface[USB_AUDIO_FEEDBACK_EP_INDEX]       =
      USB_AUDIO_PLAYBACK_IFACE;
    info.max_packet_size[USB_AUDIO_FEEDBACK_EP_INDEX] =
      USB_AUDIO_FEEDBACK_SIZE;
  }

  if (audio->configuration == USB_AUDIO_CONFIGURATION &&
      audio->recordAlt == 1)
  {
    info.type[USB_AUDIO_RECORD_DATA_EP_INDEX] = usb_redir_type_iso;
    info.interval[USB_AUDIO_RECORD_DATA_EP_INDEX] =
      USB_AUDIO_RECORD_DATA_INTERVAL;
    info.interface[USB_AUDIO_RECORD_DATA_EP_INDEX] =
      USB_AUDIO_RECORD_IFACE;
    info.max_packet_size[USB_AUDIO_RECORD_DATA_EP_INDEX] =
      USB_AUDIO_RECORD_PACKET_SIZE;
  }

  usbredirparser_send_ep_info(audio->parser, &info);
}

static void sendDeviceInfo(LG_USBAudio * audio)
{
  sendEndpointInfo(audio);
  sendInterfaceInfo(audio);
}

static void resetUSB(void * opaque)
{
  LG_USBAudio * audio = getAudio(opaque);
  resetDevice(audio);
  sendEndpointInfo(audio);
}

static void setConfiguration(void * opaque, uint64_t id,
    struct usb_redir_set_configuration_header * request)
{
  LG_USBAudio * audio = getAudio(opaque);
  struct usb_redir_configuration_status_header status =
  {
    .status        = usb_redir_stall,
    .configuration = audio->configuration,
  };

  if (request->configuration == 0 ||
      request->configuration == USB_AUDIO_CONFIGURATION)
  {
    resetDevice(audio);
    audio->configuration = request->configuration;
    status.status        = usb_redir_success;
    status.configuration = audio->configuration;
    sendDeviceInfo(audio);
  }

  usbredirparser_send_configuration_status(audio->parser, id, &status);
}

static void getConfiguration(void * opaque, uint64_t id)
{
  LG_USBAudio * audio = getAudio(opaque);
  struct usb_redir_configuration_status_header status =
  {
    .status        = usb_redir_success,
    .configuration = audio->configuration,
  };
  usbredirparser_send_configuration_status(audio->parser, id, &status);
}

static void setAltSetting(void * opaque, uint64_t id,
    struct usb_redir_set_alt_setting_header * request)
{
  LG_USBAudio * audio = getAudio(opaque);
  flushPlayback(audio);
  struct usb_redir_alt_setting_status_header status =
  {
    .status    = usb_redir_stall,
    .interface = request->interface,
    .alt       = request->alt,
  };

  if (audio->configuration == USB_AUDIO_CONFIGURATION &&
      (request->interface == USB_AUDIO_PLAYBACK_CONTROL_IFACE ||
       request->interface == USB_AUDIO_RECORD_CONTROL_IFACE) &&
      request->alt == 0)
    status.status = usb_redir_success;
  else if (audio->configuration == USB_AUDIO_CONFIGURATION &&
      request->interface == USB_AUDIO_PLAYBACK_IFACE &&
      (request->alt == 0 || getLayout(request->alt)))
  {
    stopPlaybackStreams(audio);
    audio->playbackAlt = request->alt;
    status.status      = usb_redir_success;
  }
  else if (audio->configuration == USB_AUDIO_CONFIGURATION &&
      request->interface == USB_AUDIO_RECORD_IFACE &&
      request->alt <= 1)
  {
    stopRecord(audio);
    audio->recordAlt = request->alt;
    status.status    = usb_redir_success;
  }

  if (status.status == usb_redir_success)
    sendDeviceInfo(audio);
  usbredirparser_send_alt_setting_status(audio->parser, id, &status);
}

static void getAltSetting(void * opaque, uint64_t id,
    struct usb_redir_get_alt_setting_header * request)
{
  LG_USBAudio * audio = getAudio(opaque);
  struct usb_redir_alt_setting_status_header status =
  {
    .status    = usb_redir_stall,
    .interface = request->interface,
    .alt       = 0,
  };

  if (audio->configuration == USB_AUDIO_CONFIGURATION &&
      (request->interface == USB_AUDIO_PLAYBACK_CONTROL_IFACE ||
       request->interface == USB_AUDIO_RECORD_CONTROL_IFACE))
    status.status = usb_redir_success;
  else if (audio->configuration == USB_AUDIO_CONFIGURATION &&
      request->interface == USB_AUDIO_PLAYBACK_IFACE)
  {
    status.status = usb_redir_success;
    status.alt    = audio->playbackAlt;
  }
  else if (audio->configuration == USB_AUDIO_CONFIGURATION &&
      request->interface == USB_AUDIO_RECORD_IFACE)
  {
    status.status = usb_redir_success;
    status.alt    = audio->recordAlt;
  }

  usbredirparser_send_alt_setting_status(audio->parser, id, &status);
}

static void sendISOStatus(LG_USBAudio * audio, uint64_t id,
    uint8_t endpoint, uint8_t result)
{
  struct usb_redir_iso_stream_status_header status =
  {
    .status   = result,
    .endpoint = endpoint,
  };
  usbredirparser_send_iso_stream_status(audio->parser, id, &status);
}

static void sendFeedbackPackets(LG_USBAudio * audio, uint32_t count)
{
  uint8_t data[USB_AUDIO_FEEDBACK_SIZE];
  writeLE32(data, atomic_load_explicit(
        &audio->feedbackValue, memory_order_acquire));
  struct usb_redir_iso_packet_header packet =
  {
    .endpoint = USB_AUDIO_FEEDBACK_ENDPOINT,
    .status   = usb_redir_success,
    .length   = sizeof(data),
  };

  for (uint32_t i = 0; i < count; ++i)
    usbredirparser_send_iso_packet(audio->parser,
        audio->feedbackPacketId++,
        &packet, data, USB_AUDIO_FEEDBACK_SIZE);
}

static void sendFeedbackPacketsDue(LG_USBAudio * audio)
{
  if (!atomic_load_explicit(
        &audio->feedbackStreaming, memory_order_acquire) ||
      !audio->feedbackQueueTarget)
    return;

  const uint64_t now = streamTime();
  if (now < audio->feedbackNextPacketTime)
    return;

  const uint64_t due =
    (now - audio->feedbackNextPacketTime) /
      USB_AUDIO_FEEDBACK_PERIOD_NS + 1;
  sendFeedbackPackets(audio, (uint32_t)min(
        due, (uint64_t)audio->feedbackQueueTarget));

  if (due > audio->feedbackQueueTarget)
    audio->feedbackNextPacketTime =
      now + USB_AUDIO_FEEDBACK_PERIOD_NS;
  else
    audio->feedbackNextPacketTime += due * USB_AUDIO_FEEDBACK_PERIOD_NS;
}

static void sendRecordPacketBatch(LG_USBAudio * audio, uint32_t count,
    uint64_t rateQ16, bool forceSilence, uint64_t leadingSilenceFrames,
    uint64_t availableFrames)
{
  uint8_t data[USB_AUDIO_RECORD_PACKET_SIZE];
  uint64_t underflowFrames = 0;
  struct usb_redir_iso_packet_header packet =
  {
    .endpoint = USB_AUDIO_RECORD_DATA_ENDPOINT,
    .status   = usb_redir_success,
  };

  for (uint32_t i = 0; i < count; ++i)
  {
    audio->recordPacketPhase += rateQ16;
    const uint32_t frames = (uint32_t)(audio->recordPacketPhase /
        USB_AUDIO_RECORD_RATE_DENOMINATOR);
    audio->recordPacketPhase %= USB_AUDIO_RECORD_RATE_DENOMINATOR;
    DEBUG_ASSERT(frames <= USB_AUDIO_RECORD_PACKET_FRAMES);

    const size_t size = frames * USB_AUDIO_RECORD_FRAME_SIZE;
    memset(data, 0, size);

    if (!forceSilence)
    {
      const uint32_t leading = (uint32_t)min(
          leadingSilenceFrames, (uint64_t)frames);
      leadingSilenceFrames -= leading;
      underflowFrames += leading;

      const uint32_t requested = frames - leading;
      const uint32_t available = (uint32_t)min(
          availableFrames, (uint64_t)requested);
      const int consumed = ringbuffer_consume(audio->recordBuffer,
          data + (size_t)leading * USB_AUDIO_RECORD_FRAME_SIZE,
          available);
      availableFrames -= consumed;
      underflowFrames += requested - consumed;
    }

    packet.length = (uint16_t)size;
    usbredirparser_send_iso_packet(audio->parser,
        audio->recordPacketId++, &packet, data, (int)size);
  }

  if (audio->debug && underflowFrames)
    atomic_fetch_add_explicit(&audio->recordUnderflowFrames,
        underflowFrames, memory_order_relaxed);
}

static void sendRecordRefill(LG_USBAudio * audio, uint64_t rateQ16)
{
  const uint32_t count = min(
      audio->recordRefillPackets, audio->recordBatchPackets);
  sendRecordPacketBatch(audio, count, rateQ16, true, 0, 0);
  audio->recordRefillPackets -= count;
  if (!audio->recordRefillPackets)
    audio->recordNextPacketTime =
      streamTime() + USB_AUDIO_RECORD_PACKET_INTERVAL_NS;
}

static void sendRecordPackets(LG_USBAudio * audio)
{
  if (!atomic_load_explicit(
        &audio->recordStreaming, memory_order_acquire))
    return;

  const uint64_t rateQ16 = atomic_load_explicit(
      &audio->recordRateQ16, memory_order_acquire);
  if (audio->recordRefillPackets)
  {
    sendRecordRefill(audio, rateQ16);
    return;
  }

  const uint64_t now = streamTime();
  if (audio->recordNextPacketTime > now)
    return;

  const bool overflow = atomic_exchange_explicit(
      &audio->recordOverflowPending, false, memory_order_acq_rel);
  const uint64_t lateness = now - audio->recordNextPacketTime;
  uint64_t count = lateness / USB_AUDIO_RECORD_PACKET_INTERVAL_NS + 1;
  if (audio->debug)
  {
    if (count > 1)
      atomic_fetch_add_explicit(&audio->recordLatePackets,
          count - 1, memory_order_relaxed);
    recordCounterMax(&audio->recordMaxLateness, lateness);
  }

  if (overflow || count > audio->recordSafetyPackets)
  {
    const int discarded = recordDiscardQueued(audio);
    if (audio->debug)
    {
      if (discarded)
        atomic_fetch_add_explicit(&audio->recordTrimmedFrames,
            discarded, memory_order_relaxed);
      atomic_fetch_add_explicit(&audio->recordDiscontinuities,
          1, memory_order_relaxed);
    }

    audio->recordPacketPhase   = 0;
    audio->recordRefillPackets = audio->recordSafetyPackets;
    sendRecordRefill(audio, rateQ16);
    return;
  }

  const uint32_t sendCount = (uint32_t)min(
      count, (uint64_t)audio->recordBatchPackets);
  const uint64_t catchupFrames =
    (audio->recordPacketPhase + rateQ16 * count) /
      USB_AUDIO_RECORD_RATE_DENOMINATOR;
  const uint64_t totalFrames =
    (audio->recordPacketPhase + rateQ16 * sendCount) /
      USB_AUDIO_RECORD_RATE_DENOMINATOR;
  const uint32_t sampleRate = atomic_load_explicit(
      &audio->recordSampleRate, memory_order_relaxed);
  /* Preserve the samples needed by every overdue packet. Trimming to the
   * normal target first would replace valid catch-up audio with silence. */
  const int retainFrames = max(
      (int)catchupFrames,
      max((int)(sampleRate / 50), USB_AUDIO_RECORD_PACKET_FRAMES));
  int queued = ringbuffer_getCount(audio->recordBuffer);
  if (queued > retainFrames)
  {
    const int discarded = ringbuffer_consume(
        audio->recordBuffer, NULL, queued - retainFrames);
    queued -= discarded;
    if (audio->debug && discarded)
      atomic_fetch_add_explicit(&audio->recordTrimmedFrames,
          discarded, memory_order_relaxed);
  }

  const uint64_t leadingSilenceFrames =
    totalFrames > (uint64_t)queued ? totalFrames - queued : 0;
  sendRecordPacketBatch(audio, sendCount, rateQ16, false,
      leadingSilenceFrames, queued);
  audio->recordNextPacketTime +=
    (uint64_t)sendCount * USB_AUDIO_RECORD_PACKET_INTERVAL_NS;
}

static void startISOStream(void * opaque, uint64_t id,
    struct usb_redir_start_iso_stream_header * request)
{
  LG_USBAudio * audio           = getAudio(opaque);
  flushPlayback(audio);
  uint8_t       result          = usb_redir_stall;
  uint32_t      feedbackPrefill = 0;

  if (audio->configuration == USB_AUDIO_CONFIGURATION)
  {
    switch (request->endpoint)
    {
      case USB_AUDIO_PLAYBACK_DATA_ENDPOINT:
        if (!getLayout(audio->playbackAlt))
          break;
        result = usb_redir_success;
        startPlayback(audio);
        break;

      case USB_AUDIO_FEEDBACK_ENDPOINT:
        if (!getLayout(audio->playbackAlt))
          break;
        stopFeedback(audio);
        resetFeedbackRate(audio);
        feedbackPrefill =
          (uint32_t)request->pkts_per_urb * request->no_urbs;
        if (!feedbackPrefill)
          feedbackPrefill = 1;
        else if (feedbackPrefill >
            USB_AUDIO_FEEDBACK_MAX_QUEUE)
          feedbackPrefill = USB_AUDIO_FEEDBACK_MAX_QUEUE;
        audio->feedbackQueueTarget = feedbackPrefill;
        result = usb_redir_success;
        break;

      case USB_AUDIO_RECORD_DATA_ENDPOINT:
        if (audio->recordAlt != 1)
          break;
        audio->recordBatchPackets = max(UINT32_C(1),
            min((uint32_t)request->pkts_per_urb,
              (uint32_t)USB_AUDIO_RECORD_MAX_BATCH));
        audio->recordSafetyPackets = max(UINT32_C(1),
            min((uint32_t)request->pkts_per_urb * request->no_urbs,
              (uint32_t)USB_AUDIO_RECORD_MAX_SAFETY_PACKETS));
        result = usb_redir_success;
        startRecord(audio);
        break;
    }
  }

  sendISOStatus(audio, id, request->endpoint, result);
  if (feedbackPrefill)
  {
    sendFeedbackPackets(audio, feedbackPrefill);
    audio->feedbackNextPacketTime =
      streamTime() + USB_AUDIO_FEEDBACK_PERIOD_NS;
    atomic_store_explicit(
        &audio->feedbackStreaming, true, memory_order_release);
  }
}

static void stopISOStream(void * opaque, uint64_t id,
    struct usb_redir_stop_iso_stream_header * request)
{
  LG_USBAudio * audio = getAudio(opaque);
  flushPlayback(audio);
  uint8_t result = usb_redir_stall;
  switch (request->endpoint)
  {
    case USB_AUDIO_PLAYBACK_DATA_ENDPOINT:
      stopPlayback(audio);
      result = usb_redir_success;
      break;

    case USB_AUDIO_FEEDBACK_ENDPOINT:
      stopFeedback(audio);
      result = usb_redir_success;
      break;

    case USB_AUDIO_RECORD_DATA_ENDPOINT:
      stopRecord(audio);
      result = usb_redir_success;
      break;
  }
  sendISOStatus(audio, id, request->endpoint, result);
}

static void cancelDataPacket(void * opaque, uint64_t id)
{
  (void)opaque;
  (void)id;
}

static size_t stringDescriptor(uint8_t index, uint8_t * buffer,
    size_t bufferSize)
{
  if (index == 0)
  {
    static const uint8_t language[] = { 0x04, 0x03, 0x09, 0x04 };
    memcpy(buffer, language, sizeof(language));
    return sizeof(language);
  }

  const char * text;
  switch (index)
  {
    case 1: text = "Looking Glass"            ; break;
    case 2: text = "Looking Glass USB Audio"  ; break;
    case 3: text = "LG-UAC2-0006"             ; break;
    case 4: text = "Looking Glass Microphone" ; break;
    default: return 0;
  }

  const size_t chars = strlen(text);
  const size_t size  = 2 + chars * 2;
  if (size > bufferSize)
    return 0;

  buffer[0] = (uint8_t)size;
  buffer[1] = USB_DESCRIPTOR_STRING;
  for (size_t i = 0; i < chars; ++i)
  {
    buffer[2 + i * 2] = text[i];
    buffer[3 + i * 2] = 0;
  }
  return size;
}

static void sendControlResponse(LG_USBAudio * audio, uint64_t id,
    const struct usb_redir_control_packet_header * request, uint8_t status,
    const uint8_t * data, size_t size)
{
  struct usb_redir_control_packet_header response = *request;
  response.status = status;

  if (status != usb_redir_success)
  {
    response.length = 0;
    data             = NULL;
    size             = 0;
  }
  else if (request->requesttype & 0x80)
  {
    if (size > request->length)
      size = request->length;
    response.length = (uint16_t)size;
  }
  else
  {
    response.length = request->length;
    data             = NULL;
    size             = 0;
  }

  usbredirparser_send_control_packet(audio->parser, id, &response,
      (uint8_t *)data, (int)size);
}

static void controlPacket(void * opaque, uint64_t id,
    struct usb_redir_control_packet_header * request,
    uint8_t * data, int dataLength)
{
  LG_USBAudio * audio = getAudio(opaque);
  flushPlayback(audio);
  uint8_t buffer[sizeof(l_configurationDescriptor)];
  const uint8_t * response     = NULL;
  size_t          responseSize = 0;
  uint8_t         status       = usb_redir_stall;

  if (request->requesttype == USB_REQUEST_TYPE_IN_STANDARD_DEVICE &&
      request->request == USB_REQUEST_GET_DESCRIPTOR)
  {
    const uint8_t type  = request->value >> 8;
    const uint8_t index = request->value;

    switch (type)
    {
      case USB_DESCRIPTOR_DEVICE:
        if (index == 0)
        {
          response     = l_deviceDescriptor;
          responseSize = sizeof(l_deviceDescriptor);
        }
        break;

      case USB_DESCRIPTOR_CONFIGURATION:
        if (index == 0)
        {
          response     = l_configurationDescriptor;
          responseSize = sizeof(l_configurationDescriptor);
        }
        break;

      case USB_DESCRIPTOR_STRING:
        responseSize = stringDescriptor(index, buffer, sizeof(buffer));
        if (responseSize)
          response = buffer;
        break;

      case USB_DESCRIPTOR_DEVICE_QUALIFIER:
        if (index == 0)
        {
          response     = l_deviceQualifierDescriptor;
          responseSize = sizeof(l_deviceQualifierDescriptor);
        }
        break;

      case USB_DESCRIPTOR_OTHER_SPEED:
        if (index == 0)
        {
          /* Full-speed USB cannot carry the maximum PCM24 stream. Expose a
           * functional stereo alternate sized for rates through 96 kHz. */
          const size_t playbackControlSize =
            USB_AUDIO_CONFIGURATION_DESC_SIZE +
            USB_AUDIO_FUNCTION_CONTROL_SIZE;
          memcpy(buffer, l_configurationDescriptor,
              playbackControlSize);
          size_t offset = playbackControlSize;
          memcpy(buffer + offset, l_fullSpeedPlaybackDescriptor,
              sizeof(l_fullSpeedPlaybackDescriptor));
          offset += sizeof(l_fullSpeedPlaybackDescriptor);
          memcpy(buffer + offset,
              l_configurationDescriptor +
                USB_AUDIO_RECORD_FUNCTION_OFFSET,
              USB_AUDIO_FUNCTION_CONTROL_SIZE);
          offset += USB_AUDIO_FUNCTION_CONTROL_SIZE;
          memcpy(buffer + offset, l_fullSpeedRecordDescriptor,
              sizeof(l_fullSpeedRecordDescriptor));
          buffer[1]    = USB_DESCRIPTOR_OTHER_SPEED;
          buffer[2]    = USB_AUDIO_OTHER_SPEED_SIZE & 0xff;
          buffer[3]    = USB_AUDIO_OTHER_SPEED_SIZE >> 8;
          response     = buffer;
          responseSize = USB_AUDIO_OTHER_SPEED_SIZE;
        }
        break;
    }

    if (response)
      status = usb_redir_success;
  }
  else if (request->request == USB_REQUEST_GET_STATUS &&
      request->value == 0 && request->length == sizeof(l_zeroStatus))
  {
    bool valid = false;
    switch (request->requesttype)
    {
      case USB_REQUEST_TYPE_IN_STANDARD_DEVICE:
        valid = request->index == 0;
        break;

      case USB_REQUEST_TYPE_IN_STANDARD_INTERFACE:
        valid = audio->configuration == USB_AUDIO_CONFIGURATION &&
          (request->index == USB_AUDIO_PLAYBACK_CONTROL_IFACE ||
           request->index == USB_AUDIO_PLAYBACK_IFACE ||
           request->index == USB_AUDIO_RECORD_CONTROL_IFACE ||
           request->index == USB_AUDIO_RECORD_IFACE);
        break;

      case USB_REQUEST_TYPE_IN_STANDARD_ENDPOINT:
        valid = request->index == 0x00 || request->index == 0x80 ||
          (audio->configuration == USB_AUDIO_CONFIGURATION &&
           ((getLayout(audio->playbackAlt) &&
             (request->index == USB_AUDIO_PLAYBACK_DATA_ENDPOINT ||
              request->index == USB_AUDIO_FEEDBACK_ENDPOINT)) ||
            (audio->recordAlt == 1 &&
             request->index == USB_AUDIO_RECORD_DATA_ENDPOINT)));
        break;
    }

    if (valid)
    {
      response     = l_zeroStatus;
      responseSize = sizeof(l_zeroStatus);
      status       = usb_redir_success;
    }
  }
  else if (audio->configuration == USB_AUDIO_CONFIGURATION &&
      request->requesttype == USB_REQUEST_TYPE_IN_CLASS_INTERFACE &&
      (request->index ==
         ((USB_AUDIO_PLAYBACK_CLOCK_ID << 8) |
          USB_AUDIO_PLAYBACK_CONTROL_IFACE) ||
       request->index ==
         ((USB_AUDIO_RECORD_CLOCK_ID << 8) |
          USB_AUDIO_RECORD_CONTROL_IFACE)) &&
      (uint8_t)request->value == 0)
  {
    const bool playbackClock = request->index ==
      ((USB_AUDIO_PLAYBACK_CLOCK_ID << 8) |
       USB_AUDIO_PLAYBACK_CONTROL_IFACE);
    const uint8_t control = request->value >> 8;
    if (request->request == USB_REQUEST_CUR &&
        control == USB_AUDIO_CONTROL_FREQUENCY)
    {
      writeLE32(buffer, playbackClock ?
          audio->playbackSampleRate : atomic_load_explicit(
            &audio->recordSampleRate, memory_order_relaxed));
      response     = buffer;
      responseSize = 4;
      status       = usb_redir_success;
    }
    else if (request->request == USB_REQUEST_RANGE &&
        control == USB_AUDIO_CONTROL_FREQUENCY)
    {
      responseSize = writeSampleRateRange(buffer);
      response     = buffer;
      status       = usb_redir_success;
    }
    else if (request->request == USB_REQUEST_CUR &&
        control == USB_AUDIO_CONTROL_VALIDITY)
    {
      response     = l_clockValid;
      responseSize = sizeof(l_clockValid);
      status       = usb_redir_success;
    }
  }
  else if (audio->configuration == USB_AUDIO_CONFIGURATION &&
      request->requesttype == USB_REQUEST_TYPE_OUT_CLASS_INTERFACE &&
      request->request == USB_REQUEST_CUR &&
      (request->index ==
         ((USB_AUDIO_PLAYBACK_CLOCK_ID << 8) |
          USB_AUDIO_PLAYBACK_CONTROL_IFACE) ||
       request->index ==
         ((USB_AUDIO_RECORD_CLOCK_ID << 8) |
          USB_AUDIO_RECORD_CONTROL_IFACE)) &&
      request->value == (USB_AUDIO_CONTROL_FREQUENCY << 8) &&
      request->length == 4 && dataLength == 4 && data)
  {
    const uint32_t sampleRate = nearestSampleRate(readLE32(data));
    if (request->index ==
        ((USB_AUDIO_PLAYBACK_CLOCK_ID << 8) |
         USB_AUDIO_PLAYBACK_CONTROL_IFACE))
      setPlaybackSampleRate(audio, sampleRate);
    else
      setRecordSampleRate(audio, sampleRate);
    status = usb_redir_success;
  }

  sendControlResponse(audio, id, request, status, response, responseSize);
  if (data)
    usbredirparser_free_packet_data(audio->parser, data);
}

static void isoPacket(void * opaque, uint64_t id,
    struct usb_redir_iso_packet_header * packet,
    uint8_t * data, int dataLength)
{
  (void)id;
  LG_USBAudio * audio = getAudio(opaque);
  const USBAudioLayout * layout = getLayout(audio->playbackAlt);

  if (audio->playbackStreaming)
  {
    const uint16_t frameSize =
      layout ? layout->channelCount * USB_AUDIO_SAMPLE_SIZE : 0;
    if (packet->endpoint != USB_AUDIO_PLAYBACK_DATA_ENDPOINT || !layout)
    {
      ++audio->playbackInvalidPackets;
      if (!audio->playbackErrorWarned)
      {
        DEBUG_WARN("USB audio packet loss detected; "
            "keeping the playback stream active");
        audio->playbackErrorWarned = true;
      }
    }
    else
    {
      const uint32_t expectedFrames = nextPlaybackPacketFrames(audio);
      const bool valid = packet->status == usb_redir_success &&
        dataLength > 0 && packet->length == dataLength && data &&
        dataLength <= layoutPacketSize(layout) &&
        dataLength % frameSize == 0;

      if (valid)
        queuePlaybackPacket(
            audio, data, dataLength / frameSize, frameSize);
      else
      {
        if (packet->status == usb_redir_success)
          ++audio->playbackInvalidPackets;
        ++audio->playbackConcealedPackets;
        queuePlaybackPacket(audio, NULL, expectedFrames, frameSize);

        if (!audio->playbackErrorWarned)
        {
          DEBUG_WARN("USB audio packet loss detected; "
              "keeping the playback stream active");
          audio->playbackErrorWarned = true;
        }
      }

      if (audio->playbackBatchPackets == USB_AUDIO_PLAYBACK_BATCH_PACKETS)
        flushPlayback(audio);
    }
  }

  if (data)
    usbredirparser_free_packet_data(audio->parser, data);
}

static void setupDevice(void * opaque, struct usbredirparser * parser)
{
  LG_USBAudio * audio = opaque;
  audio->parser = parser;

  parser->reset_func              = resetUSB;
  parser->set_configuration_func  = setConfiguration;
  parser->get_configuration_func  = getConfiguration;
  parser->set_alt_setting_func    = setAltSetting;
  parser->get_alt_setting_func    = getAltSetting;
  parser->start_iso_stream_func   = startISOStream;
  parser->stop_iso_stream_func    = stopISOStream;
  parser->cancel_data_packet_func = cancelDataPacket;
  parser->control_packet_func     = controlPacket;
  parser->iso_packet_func         = isoPacket;
}

static void plugDevice(void * opaque, struct usbredirparser * parser)
{
  LG_USBAudio * audio = opaque;
  audio->parser = parser;
  resetDevice(audio);
  sendDeviceInfo(audio);

  struct usb_redir_device_connect_header device =
  {
    .speed              = usb_redir_speed_high,
    .device_class       = 0xef,
    .device_subclass    = 0x02,
    .device_protocol    = 0x01,
    .vendor_id          = USB_AUDIO_VENDOR_ID,
    .product_id         = USB_AUDIO_PRODUCT_ID,
    .device_version_bcd = USB_AUDIO_DEVICE_VERSION,
  };
  usbredirparser_send_device_connect(audio->parser, &device);
}

static void unplugDevice(void * opaque)
{
  resetDevice(opaque);
}

static void reportRecordDebug(LG_USBAudio * audio)
{
  if (!audio->debug)
    return;

  const uint64_t now = streamTime();
  if (!audio->recordNextDebugTime)
  {
    audio->recordNextDebugTime =
      now + USB_AUDIO_RECORD_DEBUG_INTERVAL_NS;
    return;
  }

  if (now < audio->recordNextDebugTime)
    return;
  audio->recordNextDebugTime = now + USB_AUDIO_RECORD_DEBUG_INTERVAL_NS;

  const uint_fast64_t overflow = atomic_exchange_explicit(
      &audio->recordOverflowFrames, 0, memory_order_relaxed);
  const uint_fast64_t underflow = atomic_exchange_explicit(
      &audio->recordUnderflowFrames, 0, memory_order_relaxed);
  const uint_fast64_t trimmed = atomic_exchange_explicit(
      &audio->recordTrimmedFrames, 0, memory_order_relaxed);
  const uint_fast64_t late = atomic_exchange_explicit(
      &audio->recordLatePackets, 0, memory_order_relaxed);
  const uint_fast64_t maxLateness = atomic_exchange_explicit(
      &audio->recordMaxLateness, 0, memory_order_relaxed);
  const uint_fast64_t discontinuities = atomic_exchange_explicit(
      &audio->recordDiscontinuities, 0, memory_order_relaxed);
  if (!overflow && !underflow && !trimmed && !late && !discontinuities)
    return;

  DEBUG_INFO(
      "USB capture: overflow %" PRIuFAST64
      ", underflow %" PRIuFAST64
      ", trimmed %" PRIuFAST64
      " frames; late %" PRIuFAST64
      " packets, max %.3f ms, discontinuities %" PRIuFAST64,
      overflow, underflow, trimmed, late, maxLateness / 1000000.0,
      discontinuities);
}

static void reportPlaybackDebug(LG_USBAudio * audio)
{
  if (!audio->debug)
    return;

  const uint64_t now = streamTime();
  if (!audio->playbackNextDebugTime)
  {
    audio->playbackNextDebugTime =
      now + USB_AUDIO_PLAYBACK_DEBUG_INTERVAL_NS;
    return;
  }

  if (now < audio->playbackNextDebugTime)
    return;
  audio->playbackNextDebugTime =
    now + USB_AUDIO_PLAYBACK_DEBUG_INTERVAL_NS;

  if (!audio->playbackConcealedPackets &&
      !audio->playbackInvalidPackets)
    return;

  DEBUG_INFO("USB playback: concealed %lu"
      " packets, invalid %lu",
      audio->playbackConcealedPackets,
      audio->playbackInvalidPackets);
  audio->playbackConcealedPackets = 0;
  audio->playbackInvalidPackets   = 0;
}

static void processDevice(void * opaque, bool writable)
{
  LG_USBAudio * audio = opaque;
  const uint64_t now = streamTime();
  if (!writable)
  {
    if (atomic_load_explicit(
          &audio->recordStreaming, memory_order_acquire))
      audio->recordNextPacketTime =
        now + USB_AUDIO_RECORD_PACKET_INTERVAL_NS;
    audio->outputBackpressured = true;
  }
  else if (audio->outputBackpressured)
  {
    audio->recordNextPacketTime =
      now + USB_AUDIO_RECORD_PACKET_INTERVAL_NS;
    audio->outputBackpressured = false;
  }

  if (writable)
    sendFeedbackPacketsDue(audio);
  flushPlayback(audio);
  if (writable)
    sendRecordPackets(audio);
  reportPlaybackDebug(audio);
  reportRecordDebug(audio);
}

static const LG_USBRedirDeviceOps l_deviceOps =
{
  .setup   = setupDevice,
  .plug    = plugDevice,
  .process = processDevice,
  .unplug  = unplugDevice,
};

LG_USBAudio * lgUsbAudio_create(const LG_USBAudioEventOps * events,
    void * eventOpaque, bool debug)
{
  LG_USBAudio * audio = calloc(1, sizeof(*audio));
  if (!audio)
    return NULL;

  audio->events              = events;
  audio->eventOpaque         = eventOpaque;
  audio->debug               = debug;
  audio->playbackSampleRate  = LG_USB_AUDIO_DEFAULT_SAMPLE_RATE;
  audio->recordBatchPackets  = 1;
  audio->recordSafetyPackets = 1;
  audio->recordIdle = lgCreateEvent(true, 0);
  if (!audio->recordIdle)
  {
    free(audio);
    return NULL;
  }
  audio->recordBuffer        = ringbuffer_new(
      USB_AUDIO_RECORD_QUEUE_FRAMES, USB_AUDIO_RECORD_FRAME_SIZE);
  if (!audio->recordBuffer)
  {
    lgFreeEvent(audio->recordIdle);
    free(audio);
    return NULL;
  }

  atomic_init(&audio->feedbackValue,
      encodeFeedbackRate(audio->playbackSampleRate));
  atomic_init(&audio->feedbackStreaming, false);
  atomic_init(
      &audio->recordSampleRate, LG_USB_AUDIO_DEFAULT_SAMPLE_RATE);
  atomic_init(&audio->recordStreaming, false);
  atomic_init(&audio->recordAccepting, false);
  atomic_init(&audio->recordOverflowPending, false);
  atomic_init(&audio->recordGeneration, 0);
  atomic_init(&audio->recordInFlight, 0);
  atomic_init(&audio->recordRateQ16,
      (uint64_t)LG_USB_AUDIO_DEFAULT_SAMPLE_RATE *
        USB_AUDIO_RECORD_RATE_SCALE);
  atomic_init(&audio->recordOverflowFrames, 0);
  atomic_init(&audio->recordUnderflowFrames, 0);
  atomic_init(&audio->recordTrimmedFrames, 0);
  atomic_init(&audio->recordLatePackets, 0);
  atomic_init(&audio->recordMaxLateness, 0);
  atomic_init(&audio->recordDiscontinuities, 0);
  return audio;
}

void lgUsbAudio_setFeedbackRate(LG_USBAudio * audio, double sampleRate)
{
  if (!audio)
    return;

  atomic_store_explicit(&audio->feedbackValue,
      encodeFeedbackRate(sampleRate), memory_order_release);
}

bool lgUsbAudio_feedbackActive(const LG_USBAudio * audio)
{
  return audio && atomic_load_explicit(
      &audio->feedbackStreaming, memory_order_acquire);
}

bool lgUsbAudio_recordData(
    LG_USBAudio * audio, const void * data, size_t frames,
    const LG_AudioClock * sourceClock)
{
  if (!audio || frames > INT_MAX || (frames && !data))
    return false;

  const unsigned int generation = atomic_load_explicit(
      &audio->recordGeneration, memory_order_seq_cst);
  if (!atomic_load_explicit(
        &audio->recordStreaming, memory_order_acquire) ||
      !atomic_load_explicit(
        &audio->recordAccepting, memory_order_seq_cst))
    return false;

  atomic_fetch_add_explicit(
      &audio->recordInFlight, 1, memory_order_seq_cst);
  if (!atomic_load_explicit(
        &audio->recordStreaming, memory_order_acquire) ||
      !atomic_load_explicit(
        &audio->recordAccepting, memory_order_seq_cst) ||
      atomic_load_explicit(
        &audio->recordGeneration, memory_order_seq_cst) != generation)
  {
    const unsigned int previous = atomic_fetch_sub_explicit(
        &audio->recordInFlight, 1, memory_order_seq_cst);
    if (previous == 1 && !atomic_load_explicit(
          &audio->recordAccepting, memory_order_seq_cst))
      lgSignalEvent(audio->recordIdle);
    return false;
  }

  const size_t receivedFrames = frames;
  const uint32_t sampleRate = atomic_load_explicit(
      &audio->recordSampleRate, memory_order_relaxed);
  bool sourceContinuous = true;
  if (sourceClock)
    sourceContinuous = updateRecordSourceRate(
        audio, sampleRate, sourceClock);
  else
    updateRecordArrivalRate(audio, sampleRate, receivedFrames);

  size_t overflowFrames = 0;
  if (sourceContinuous)
  {
    const int length = ringbuffer_getLength(audio->recordBuffer);
    const uint8_t * input = data;
    if (frames > (size_t)length)
    {
      input += (frames - length) * USB_AUDIO_RECORD_FRAME_SIZE;
      frames = length;
    }

    const int appended = ringbuffer_append(
        audio->recordBuffer, input, (int)frames);
    overflowFrames = receivedFrames - appended;
  }

  if (!sourceContinuous || overflowFrames)
  {
    atomic_store_explicit(
        &audio->recordOverflowPending, true, memory_order_release);
    if (audio->debug)
      atomic_fetch_add_explicit(&audio->recordOverflowFrames,
          overflowFrames, memory_order_relaxed);
  }

  const unsigned int previous = atomic_fetch_sub_explicit(
      &audio->recordInFlight, 1, memory_order_seq_cst);
  if (previous == 1 && !atomic_load_explicit(
        &audio->recordAccepting, memory_order_seq_cst))
    lgSignalEvent(audio->recordIdle);
  return !sourceContinuous || !overflowFrames;
}

uint64_t lgUsbAudio_processDelayNs(const LG_USBAudio * audio)
{
  if (!audio)
    return UINT64_MAX;

  if (audio->outputBackpressured)
    return UINT64_MAX;

  const uint64_t now = streamTime();
  uint64_t delay = UINT64_MAX;

  if (atomic_load_explicit(
        &audio->feedbackStreaming, memory_order_acquire))
    delay = audio->feedbackNextPacketTime > now ?
      audio->feedbackNextPacketTime - now : 0;

  if (!atomic_load_explicit(
        &audio->recordStreaming, memory_order_acquire))
    return delay;

  uint64_t recordDelay = 0;
  if (!audio->recordRefillPackets)
  {
    const uint64_t deadline = audio->recordNextPacketTime +
      (uint64_t)(audio->recordBatchPackets - 1) *
        USB_AUDIO_RECORD_PACKET_INTERVAL_NS;
    recordDelay = deadline > now ? deadline - now : 0;
  }

  return min(delay, recordDelay);
}

void lgUsbAudio_destroy(LG_USBAudio * audio)
{
  if (!audio)
    return;

  recordDisableInput(audio);
  recordWaitForInput(audio);
  ringbuffer_free(&audio->recordBuffer);
  lgFreeEvent(audio->recordIdle);
  free(audio);
}

const LG_USBRedirDeviceOps * lgUsbAudio_deviceOps(void)
{
  return &l_deviceOps;
}
