|
|
|
@@ -1,7 +1,4 @@
|
|
|
|
|
/*
|
|
|
|
|
* audio/audio_engine.h: capture/playback + DSP + jitter buffer + mixer.
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
/* Capture, playback, jitter buffering, and mixing. */
|
|
|
|
|
#ifndef VOICECAT_AUDIO_AUDIO_ENGINE_H
|
|
|
|
|
#define VOICECAT_AUDIO_AUDIO_ENGINE_H
|
|
|
|
|
|
|
|
|
@@ -394,13 +391,8 @@ class AudioEngine {
|
|
|
|
|
std::atomic<bool> running_{false};
|
|
|
|
|
std::atomic<float> output_volume_{1.0f};
|
|
|
|
|
|
|
|
|
|
// Capture-side frame accumulators: miniaudio fires the capture (and loopback) callback at
|
|
|
|
|
// whatever period the hardware/driver chooses — commonly 480 samples (10 ms) on WASAPI
|
|
|
|
|
// shared mode, while the Opus encoder requires exactly frame_samples_ per call (960 for
|
|
|
|
|
// 20 ms @ 48 kHz). Accumulate incoming PCM until a full frame is ready, then call
|
|
|
|
|
// capture_cb_. This mirrors the RemoteStream::ring fix on the playback side. Both
|
|
|
|
|
// accumulators are pre-allocated once in start(); never resized from the RT callback
|
|
|
|
|
// thread (satisfies architecture.md §3 — no allocation on RT threads).
|
|
|
|
|
// Device callback periods are independent of codec frame size. These preallocated
|
|
|
|
|
// accumulators emit complete frames without allocating on an RT thread.
|
|
|
|
|
struct CaptureAccum {
|
|
|
|
|
std::vector<int16_t> buf; // pre-sized to frame_samples_ in start()
|
|
|
|
|
int count = 0;
|
|
|
|
@@ -428,15 +420,10 @@ class AudioEngine {
|
|
|
|
|
float gain = 1.0f;
|
|
|
|
|
bool mute = false;
|
|
|
|
|
uint32_t playout_ts = 0;
|
|
|
|
|
// playout_ts free-runs (advances every callback via PLC), so it must be seeded from, and
|
|
|
|
|
// periodically re-synced to, the actual stream timeline — otherwise it drifts past the
|
|
|
|
|
// jitter buffer's drop window across VAD/PTT gaps and late joins and every frame is
|
|
|
|
|
// dropped/never-due (silent playback). false until the first frame seeds it (on_playback).
|
|
|
|
|
// Re-seeded from the stream timeline after late joins and transmission gaps.
|
|
|
|
|
bool playout_started = false;
|
|
|
|
|
|
|
|
|
|
// Set by push_recv_frame when a kFlagMarker (talkspurt-start) frame arrives; consumed by
|
|
|
|
|
// on_playback to force an immediate playout-clock reseed at the new talkspurt, so the
|
|
|
|
|
// bounded-depth target is re-established cleanly across silence gaps. See on_playback.
|
|
|
|
|
// A talkspurt marker forces playout-clock reseeding.
|
|
|
|
|
bool pending_marker = false;
|
|
|
|
|
|
|
|
|
|
// Diagnostic: times the decode/playback ring underran (produced silence because the
|
|
|
|
@@ -444,12 +431,7 @@ class AudioEngine {
|
|
|
|
|
// "frames arriving but silent / latency starved" signal. Polled via stream_underruns().
|
|
|
|
|
std::atomic<uint64_t> underruns{0};
|
|
|
|
|
|
|
|
|
|
// PLC cap (defense-in-depth): consecutive samples produced by packet-loss
|
|
|
|
|
// concealment since the last real decoded frame. Reset to 0 on every real frame.
|
|
|
|
|
// When it exceeds kPlcCapSamples (audio_engine.cpp), on_playback stops calling
|
|
|
|
|
// opus_decode(nullptr,0,...) and emits silence instead — bounding the comfort-noise
|
|
|
|
|
// hiss to ~2 s so a stale stream can never hiss forever even if remove_stream is
|
|
|
|
|
// never called. See on_playback's decode loop.
|
|
|
|
|
// Bounds consecutive PLC output so a stale stream eventually becomes silent.
|
|
|
|
|
int64_t plc_samples_since_real = 0;
|
|
|
|
|
|
|
|
|
|
// Listener-chosen, local-only noise reduction (docs/voice.md §10). Lazily
|
|
|
|
@@ -483,17 +465,8 @@ class AudioEngine {
|
|
|
|
|
std::atomic<int64_t> last_voice_ms{0};
|
|
|
|
|
bool talking = false;
|
|
|
|
|
|
|
|
|
|
// Decode/playback decoupling ring
|
|
|
|
|
// opus_decode() must be called with max_samples == the encoder's fixed frame size
|
|
|
|
|
// (decoder.frame_samples(), e.g. 960 @ 20ms/48kHz) — that's a property of the bitstream,
|
|
|
|
|
// not a choice. miniaudio's playback callback period is a *separate*, independently
|
|
|
|
|
// chosen value (often smaller, e.g. ~480 @ low-latency WASAPI defaults) and must never
|
|
|
|
|
// be passed to opus_decode as max_samples (doing so made decode fail basically every
|
|
|
|
|
// callback — silent playback bug, fixed by this ring). on_playback() tops this ring up
|
|
|
|
|
// by decoding whole Opus frames (decoder's channel count) and drains exactly the
|
|
|
|
|
// hardware-requested sample count from it each callback, padding with silence (PLC) on
|
|
|
|
|
// underrun. Sized once in init_ring() (called off the audio thread); never resized from
|
|
|
|
|
// on_playback (real-time rule).
|
|
|
|
|
// Decoding uses the bitstream frame size, while playback drains the device callback
|
|
|
|
|
// size. This preallocated ring decouples those clocks and is never resized on the RT path.
|
|
|
|
|
std::vector<int16_t> ring; // capacity = (frame_samples * 8) frames * ring_channels
|
|
|
|
|
size_t ring_channels = 1;
|
|
|
|
|
size_t ring_head = 0; // next frame (sample-per-channel) to read
|
|
|
|
|