chore: remove skeleton build mode and stub #ifdef scaffolding
Drop the M0 no-deps skeleton preset and all VOICECAT_HAS_NET/AUDIO/OPUS/NS guards that it required. Every subsystem is fully implemented; the stub #else paths were dead code that added noise to every header and source file. - CMakePresets.json: remove skeleton configure/build/test entries - CMakeLists.txt (root/core/tests): remove VOICECAT_USE_VCPKG_DEPS option and guards; all targets now build unconditionally - 17 C++ source files: unwrap HAS_* guards, delete stub #else blocks - apm_processor.cpp: delete ApmPassthrough no-op class; create() always returns RnnoiseProcessor - 18 test files: remove HAS_* guards and stub int main() skip bodies - docs/building.md: remove skeleton from preset table and prose VOICECAT_HAS_LOOPBACK (Windows WASAPI loopback platform gate) unchanged. 29/29 ctest green. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,9 +5,7 @@
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
|
||||
#ifdef VOICECAT_HAS_NS
|
||||
#include "rnnoise.h"
|
||||
#endif
|
||||
|
||||
namespace voicecat::audio {
|
||||
|
||||
@@ -19,15 +17,6 @@ int64_t steady_now_ms() {
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// ── ApmPassthrough ────────────────────────────────────────────────────────────
|
||||
// No-op: returns true (VAD always open), does not modify PCM.
|
||||
// Replaced by WebrtcApmProcessor when VOICECAT_HAS_APM is defined.
|
||||
class ApmPassthrough final : public ApmProcessor {
|
||||
public:
|
||||
void process_render(const int16_t*, int, int) override {}
|
||||
bool process_capture(int16_t*, int, int) override { return true; }
|
||||
};
|
||||
|
||||
// ── EnergyVadProcessor ──────────────────────────────────────────────────────
|
||||
// Lightweight, dependency-free energy/RMS VAD — see apm_processor.h's create_vad() doc comment
|
||||
// for why this exists instead of a real APM. No AEC (process_render is a no-op); doesn't modify
|
||||
@@ -62,7 +51,6 @@ class EnergyVadProcessor final : public ApmProcessor {
|
||||
int64_t last_voice_ms_ = 0; // epoch start -> gate begins closed until first loud frame
|
||||
};
|
||||
|
||||
#ifdef VOICECAT_HAS_NS
|
||||
// ── RnnoiseProcessor ─────────────────────────────────────────────────────────
|
||||
// Real noise suppression via vendored RNNoise (third_party/rnnoise; docs/voice.md §10-11).
|
||||
// RNNoise is a mono, 48 kHz, fixed 480-sample (10 ms) speech denoiser; our engine clock is fixed
|
||||
@@ -103,14 +91,9 @@ class RnnoiseProcessor final : public ApmProcessor {
|
||||
float in_[kFrame];
|
||||
float out_[kFrame];
|
||||
};
|
||||
#endif // VOICECAT_HAS_NS
|
||||
|
||||
std::unique_ptr<ApmProcessor> ApmProcessor::create() {
|
||||
#ifdef VOICECAT_HAS_NS
|
||||
return std::make_unique<RnnoiseProcessor>();
|
||||
#else
|
||||
return std::make_unique<ApmPassthrough>();
|
||||
#endif
|
||||
}
|
||||
|
||||
std::unique_ptr<ApmProcessor> ApmProcessor::create_vad(float rms_threshold,
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#ifdef VOICECAT_HAS_AUDIO
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include <miniaudio.h>
|
||||
#endif
|
||||
|
||||
#include "audio/audio_engine.h"
|
||||
|
||||
@@ -41,7 +39,6 @@ constexpr int32_t kStarveSamples = 48000 * 120 / 1000; // reseed when clock
|
||||
// defense-in-depth against any future regression that skips remove_stream.
|
||||
constexpr int32_t kPlcCapSamples = 48000 * 2; // 2 s @ 48 kHz
|
||||
|
||||
#ifdef VOICECAT_HAS_AUDIO
|
||||
// device_id encoding (DeviceInfo::id / AudioParams::*_device_id): a hex string of the raw
|
||||
// ma_device_id bytes. Opaque on purpose — names aren't guaranteed unique, and this is the only
|
||||
// stable handle miniaudio accepts back for device selection. Internal contract only; never
|
||||
@@ -76,7 +73,6 @@ bool hex_decode_device_id(const std::string& hex, ma_device_id* out) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif // VOICECAT_HAS_AUDIO
|
||||
} // namespace
|
||||
|
||||
// ── JitterBuffer ─────────────────────────────────────────────────────────────
|
||||
@@ -197,19 +193,14 @@ AudioEngine::AudioEngine() = default;
|
||||
|
||||
AudioEngine::~AudioEngine() {
|
||||
stop();
|
||||
#ifdef VOICECAT_HAS_AUDIO
|
||||
stop_loopback_capture(); // loopback has an independent lifecycle — close it before the context
|
||||
if (context_inited_) {
|
||||
ma_context_uninit(&context_);
|
||||
context_inited_ = false;
|
||||
}
|
||||
#endif
|
||||
#ifdef VOICECAT_HAS_OPUS
|
||||
if (dred_dec_) { opus_dred_decoder_destroy(dred_dec_); dred_dec_ = nullptr; }
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef VOICECAT_HAS_AUDIO
|
||||
ma_context_config AudioEngine::make_context_config() {
|
||||
ma_context_config cfg = ma_context_config_init();
|
||||
// iOS: leave AVAudioSession entirely to the Swift layer (IOSAudioRouter). See the
|
||||
@@ -219,7 +210,6 @@ ma_context_config AudioEngine::make_context_config() {
|
||||
cfg.coreaudio.noAudioSessionDeactivate = MA_TRUE; // don't setActive(false) on device uninit
|
||||
return cfg;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool AudioEngine::start(const AudioParams& p, CaptureCallback capture_cb) {
|
||||
if (running_.load()) return false;
|
||||
@@ -227,14 +217,11 @@ bool AudioEngine::start(const AudioParams& p, CaptureCallback capture_cb) {
|
||||
capture_cb_ = std::move(capture_cb);
|
||||
frame_samples_ = static_cast<int>(p.sample_rate / 1000 * p.frame_ms);
|
||||
running_.store(true, std::memory_order_release);
|
||||
#ifdef VOICECAT_HAS_OPUS
|
||||
if (!dred_dec_) {
|
||||
int err = 0;
|
||||
dred_dec_ = opus_dred_decoder_create(&err); // null on failure — DRED silently disabled
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef VOICECAT_HAS_AUDIO
|
||||
// Pre-allocate capture accumulators before the devices start so on_capture / on_loopback
|
||||
// never allocate on the RT thread. count=0 means "empty"; the buf is sized to exactly one
|
||||
// encoder frame so a memcpy into it can never overrun. The mic accumulator is sized to
|
||||
@@ -247,9 +234,7 @@ bool AudioEngine::start(const AudioParams& p, CaptureCallback capture_cb) {
|
||||
capture_accum_.count = 0;
|
||||
loopback_accum_.buf.assign(static_cast<size_t>(frame_samples_), 0);
|
||||
loopback_accum_.count = 0;
|
||||
#endif
|
||||
|
||||
#ifdef VOICECAT_HAS_AUDIO
|
||||
// Own the ma_context (lazily, reused across restarts) so miniaudio does not touch
|
||||
// AVAudioSession on iOS — the Swift IOSAudioRouter is the sole session owner. Without
|
||||
// this, ma_device_init(nullptr, ...) below would reset the session category to Record/
|
||||
@@ -342,14 +327,12 @@ bool AudioEngine::start(const AudioParams& p, CaptureCallback capture_cb) {
|
||||
mixer_timer_stop_.store(false, std::memory_order_release);
|
||||
mixer_timer_thread_ = std::thread([this] { run_mixer_timer(); });
|
||||
}
|
||||
#endif // VOICECAT_HAS_AUDIO
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<DeviceInfo> AudioEngine::enumerate_devices(bool capture) {
|
||||
std::vector<DeviceInfo> result;
|
||||
#ifdef VOICECAT_HAS_AUDIO
|
||||
// Use the no-AVAudioSession-management config here too: device enumeration runs at
|
||||
// SessionState init (refreshDevices) and on settings views, and a default-config context
|
||||
// would call setCategory()/setActive() on iOS, disrupting the session the Swift layer owns.
|
||||
@@ -377,16 +360,12 @@ std::vector<DeviceInfo> AudioEngine::enumerate_devices(bool capture) {
|
||||
}
|
||||
|
||||
ma_context_uninit(&ctx);
|
||||
#else
|
||||
(void)capture;
|
||||
#endif // VOICECAT_HAS_AUDIO
|
||||
return result;
|
||||
}
|
||||
|
||||
void AudioEngine::stop() {
|
||||
if (!running_.exchange(false)) return;
|
||||
|
||||
#ifdef VOICECAT_HAS_AUDIO
|
||||
// External playback: stop + join the mixer-timer thread before tearing down state it reads.
|
||||
mixer_timer_stop_.store(true, std::memory_order_release);
|
||||
if (mixer_timer_thread_.joinable()) mixer_timer_thread_.join();
|
||||
@@ -400,11 +379,9 @@ void AudioEngine::stop() {
|
||||
ma_device_uninit(&playback_device_);
|
||||
playback_started_ = false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool AudioEngine::suspend() {
|
||||
#ifdef VOICECAT_HAS_AUDIO
|
||||
if (!running_.load(std::memory_order_acquire)) return true;
|
||||
// External playback: pause the mixer timer (there is no playback device to stop). This
|
||||
// matches the VPIO renderer going down during an AVAudioSession interruption.
|
||||
@@ -416,13 +393,9 @@ bool AudioEngine::suspend() {
|
||||
if (capture_started_) ok &= (ma_device_stop(&capture_device_) == MA_SUCCESS);
|
||||
if (playback_started_) ok &= (ma_device_stop(&playback_device_) == MA_SUCCESS);
|
||||
return ok;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool AudioEngine::resume() {
|
||||
#ifdef VOICECAT_HAS_AUDIO
|
||||
if (!running_.load(std::memory_order_acquire)) return true;
|
||||
// External playback: relaunch the mixer timer (mixer_scratch_ is still sized from start()).
|
||||
if (external_playback_ && !mixer_timer_thread_.joinable()) {
|
||||
@@ -433,9 +406,6 @@ bool AudioEngine::resume() {
|
||||
if (capture_started_) ok &= (ma_device_start(&capture_device_) == MA_SUCCESS);
|
||||
if (playback_started_) ok &= (ma_device_start(&playback_device_) == MA_SUCCESS);
|
||||
return ok;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void AudioEngine::inject_capture(int kind, const int16_t* pcm, size_t samples_per_channel,
|
||||
@@ -530,12 +500,10 @@ void AudioEngine::remove_stream(uint32_t ssrc) {
|
||||
std::lock_guard lk(streams_mu_);
|
||||
auto it = streams_.find(ssrc);
|
||||
if (it != streams_.end()) {
|
||||
#ifdef VOICECAT_HAS_OPUS
|
||||
if (it->second.dred_state_) {
|
||||
opus_dred_free(it->second.dred_state_);
|
||||
it->second.dred_state_ = nullptr;
|
||||
}
|
||||
#endif
|
||||
streams_.erase(it);
|
||||
}
|
||||
}
|
||||
@@ -590,7 +558,6 @@ int32_t AudioEngine::stream_playout_depth_samples(uint32_t ssrc) const {
|
||||
return static_cast<int32_t>(*newest - it->second.playout_ts);
|
||||
}
|
||||
|
||||
#ifdef VOICECAT_HAS_OPUS
|
||||
void AudioEngine::init_recv_stream(uint32_t ssrc, const codec::OpusParams& p,
|
||||
uint32_t user_id, uint32_t stream_id, bool is_voice) {
|
||||
std::lock_guard lk(streams_mu_);
|
||||
@@ -616,7 +583,6 @@ void AudioEngine::init_recv_stream(uint32_t ssrc, const codec::OpusParams& p,
|
||||
stream.dred_state_ = opus_dred_alloc(&err); // null on failure — falls back to PLC
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void AudioEngine::set_pcm_sink(PcmSink cb, void* user) {
|
||||
pcm_sink_user_.store(user, std::memory_order_relaxed);
|
||||
@@ -628,8 +594,6 @@ void AudioEngine::set_mixed_output_sink(MixedSink cb, void* user) {
|
||||
mixed_sink_.store(cb, std::memory_order_release);
|
||||
}
|
||||
|
||||
#ifdef VOICECAT_HAS_AUDIO
|
||||
|
||||
void AudioEngine::capture_data_cb(ma_device* dev, void* /*out*/,
|
||||
const void* in, ma_uint32 frame_count) {
|
||||
auto* self = static_cast<AudioEngine*>(dev->pUserData);
|
||||
@@ -678,7 +642,6 @@ void AudioEngine::on_playback(int16_t* out, ma_uint32 frames) {
|
||||
std::unique_lock lk(streams_mu_, std::try_to_lock);
|
||||
if (!lk) return; // contended: emit silence this period
|
||||
|
||||
#ifdef VOICECAT_HAS_OPUS
|
||||
std::vector<int32_t> mix(frames * pb_channels, 0);
|
||||
|
||||
for (auto& [ssrc, stream] : streams_) {
|
||||
@@ -747,7 +710,6 @@ void AudioEngine::on_playback(int16_t* out, ma_uint32 frames) {
|
||||
// embeds in the next packet) → PLC comfort noise. DRED and FEC both need the
|
||||
// *next* packet already buffered, so copy it once and try each in turn.
|
||||
n = -1;
|
||||
#ifdef VOICECAT_HAS_OPUS
|
||||
uint32_t next_ts = stream.playout_ts + static_cast<uint32_t>(frame_samples);
|
||||
size_t psz = stream.jitter.try_copy_front_payload(
|
||||
next_ts, stream.dred_payload_scratch_.data(),
|
||||
@@ -777,7 +739,6 @@ void AudioEngine::on_playback(int16_t* out, ma_uint32 frames) {
|
||||
stream.dred_payload_scratch_.data(), static_cast<int>(psz),
|
||||
stream.decode_scratch.data(), frame_samples, /*fec=*/true);
|
||||
}
|
||||
#endif
|
||||
// 3. PLC: synthesize a continuation when no redundancy is available.
|
||||
if (n <= 0) {
|
||||
n = stream.decoder.decode(nullptr, 0, stream.decode_scratch.data(),
|
||||
@@ -858,10 +819,6 @@ void AudioEngine::on_playback(int16_t* out, ma_uint32 frames) {
|
||||
int32_t s = static_cast<int32_t>(static_cast<float>(mix[i]) * ovol);
|
||||
out[i] = static_cast<int16_t>(std::clamp(s, -32768, 32767));
|
||||
}
|
||||
#else
|
||||
(void)out;
|
||||
(void)frames;
|
||||
#endif
|
||||
}
|
||||
|
||||
// External-playback timer (iOS VPIO): with no hardware playback device to "pull" frames, this
|
||||
@@ -986,11 +943,4 @@ bool AudioEngine::start_loopback_capture(int /*kind*/, int /*channels*/) { retur
|
||||
void AudioEngine::stop_loopback_capture() {}
|
||||
#endif // VOICECAT_HAS_LOOPBACK
|
||||
|
||||
#endif // VOICECAT_HAS_AUDIO
|
||||
|
||||
#ifndef VOICECAT_HAS_AUDIO
|
||||
bool AudioEngine::start_loopback_capture(int /*kind*/, int /*channels*/) { return false; }
|
||||
void AudioEngine::stop_loopback_capture() {}
|
||||
#endif
|
||||
|
||||
} // namespace voicecat::audio
|
||||
|
||||
@@ -25,14 +25,10 @@
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#ifdef VOICECAT_HAS_AUDIO
|
||||
// miniaudio single-header — MINIAUDIO_IMPLEMENTATION defined in audio_engine.cpp
|
||||
#include <miniaudio.h>
|
||||
#endif
|
||||
|
||||
#ifdef VOICECAT_HAS_OPUS
|
||||
#include "codec/opus_codec.h"
|
||||
#endif
|
||||
|
||||
#include "audio/apm_processor.h"
|
||||
|
||||
@@ -222,7 +218,6 @@ class AudioEngine {
|
||||
// bounded-depth invariant. Returns 0 if the stream is unknown or not yet playing out.
|
||||
int32_t stream_playout_depth_samples(uint32_t ssrc) const;
|
||||
|
||||
#ifdef VOICECAT_HAS_OPUS
|
||||
// Configure the Opus decoder for an incoming ssrc (must be called before
|
||||
// push_recv_frame for that ssrc). user_id/stream_id identify the source for the
|
||||
// pcm_sink_ callback. is_voice marks a MIC stream so the receive-side NR pass knows it may
|
||||
@@ -230,7 +225,6 @@ class AudioEngine {
|
||||
// Thread-safe.
|
||||
void init_recv_stream(uint32_t ssrc, const codec::OpusParams& p,
|
||||
uint32_t user_id, uint32_t stream_id, bool is_voice);
|
||||
#endif
|
||||
|
||||
// External PCM tap: callback fired once per decoded Opus frame per remote stream, on the
|
||||
// playback (RT) thread. Matching signature to vc_pcm_sink_cb (cast at the C-ABI boundary).
|
||||
@@ -254,7 +248,6 @@ class AudioEngine {
|
||||
// is samples per channel; total samples written = samples_per_channel * channels.
|
||||
void inject_capture(int kind, const int16_t* pcm, size_t samples_per_channel, int channels);
|
||||
|
||||
#ifdef VOICECAT_HAS_AUDIO
|
||||
// TEST-ONLY — exposes the playback mixer without a real ma_device, so tests can verify
|
||||
// stereo mixing end-to-end (no audio hardware needed). Same logic the real playback
|
||||
// callback uses; safe to call any time after start() (no ma_device touched).
|
||||
@@ -312,7 +305,6 @@ class AudioEngine {
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef VOICECAT_HAS_LOOPBACK
|
||||
// TEST-ONLY — drives the loopback accumulator directly with an explicit callback, the
|
||||
@@ -354,7 +346,6 @@ class AudioEngine {
|
||||
#endif
|
||||
|
||||
private:
|
||||
#ifdef VOICECAT_HAS_AUDIO
|
||||
static void capture_data_cb(ma_device*, void*, const void*, ma_uint32);
|
||||
static void playback_data_cb(ma_device*, void*, const void*, ma_uint32);
|
||||
void on_capture(const int16_t* pcm, ma_uint32 frames);
|
||||
@@ -403,7 +394,6 @@ class AudioEngine {
|
||||
bool loopback_started_ = false;
|
||||
int loopback_kind_ = 0;
|
||||
int loopback_channels_ = 1; // channel count the loopback device was opened with
|
||||
#endif
|
||||
#endif
|
||||
|
||||
AudioParams params_{};
|
||||
@@ -441,9 +431,7 @@ class AudioEngine {
|
||||
// Per remote stream (protected by streams_mu_).
|
||||
struct RemoteStream {
|
||||
JitterBuffer jitter;
|
||||
#ifdef VOICECAT_HAS_OPUS
|
||||
codec::OpusDecoder decoder;
|
||||
#endif
|
||||
float gain = 1.0f;
|
||||
bool mute = false;
|
||||
uint32_t playout_ts = 0;
|
||||
@@ -484,9 +472,7 @@ class AudioEngine {
|
||||
|
||||
// DRED: pre-allocated scratch for loss recovery. dred_state_ is per-stream; see
|
||||
// AudioEngine::dred_dec_ (shared). Allocated in init_recv_stream(); freed in remove_stream().
|
||||
#ifdef VOICECAT_HAS_OPUS
|
||||
::OpusDRED* dred_state_ = nullptr;
|
||||
#endif
|
||||
std::vector<uint8_t> dred_payload_scratch_; // pre-sized to 4000 bytes
|
||||
|
||||
// In-band FEC: whether the sender negotiated OPUS_SET_INBAND_FEC for this stream.
|
||||
@@ -574,9 +560,7 @@ class AudioEngine {
|
||||
std::atomic<void*> mixed_sink_user_{nullptr};
|
||||
bool external_playback_ = false;
|
||||
|
||||
#ifdef VOICECAT_HAS_OPUS
|
||||
::OpusDREDDecoder* dred_dec_ = nullptr; // shared DRED decoder; null if unsupported
|
||||
#endif
|
||||
|
||||
static constexpr int64_t kTalkHangoverMs = 300;
|
||||
};
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
namespace voicecat::codec {
|
||||
|
||||
#ifdef VOICECAT_HAS_OPUS
|
||||
|
||||
// Map an intended channel/capture sample rate (Hz) to the Opus max-bandwidth constant. The
|
||||
// codec always runs at 48 kHz internally (docs/voice.md §3); this caps the bandwidth the
|
||||
// encoder will select so a channel can request narrowband/wideband audio for low-bitrate rooms.
|
||||
@@ -103,16 +101,4 @@ void OpusDecoder::destroy() {
|
||||
if (dec_) { opus_decoder_destroy(dec_); dec_ = nullptr; }
|
||||
}
|
||||
|
||||
#else // !VOICECAT_HAS_OPUS — stubs
|
||||
|
||||
bool OpusEncoder::init(const OpusParams&) { err_ = "OPUS not compiled in"; return false; }
|
||||
int OpusEncoder::encode(const int16_t*, int, uint8_t*, int) { return -1; }
|
||||
void OpusEncoder::destroy() {}
|
||||
|
||||
bool OpusDecoder::init(const OpusParams&) { err_ = "OPUS not compiled in"; return false; }
|
||||
int OpusDecoder::decode(const uint8_t*, int, int16_t*, int, bool) { return -1; }
|
||||
void OpusDecoder::destroy() {}
|
||||
|
||||
#endif // VOICECAT_HAS_OPUS
|
||||
|
||||
} // namespace voicecat::codec
|
||||
|
||||
@@ -10,9 +10,7 @@
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#ifdef VOICECAT_HAS_OPUS
|
||||
#include <opus/opus.h>
|
||||
#endif
|
||||
|
||||
namespace voicecat::codec {
|
||||
|
||||
@@ -73,11 +71,7 @@ class OpusEncoder {
|
||||
const char* error_string() const { return err_; }
|
||||
|
||||
private:
|
||||
#ifdef VOICECAT_HAS_OPUS
|
||||
::OpusEncoder* enc_ = nullptr;
|
||||
#else
|
||||
void* enc_ = nullptr;
|
||||
#endif
|
||||
int frame_samples_ = 0;
|
||||
int channels_ = 1;
|
||||
const char* err_ = nullptr;
|
||||
@@ -104,10 +98,8 @@ class OpusDecoder {
|
||||
// Decode a lost frame using pre-parsed DRED state from the next received packet.
|
||||
// dred_offset=0 means the frame immediately before the next packet.
|
||||
// Returns frame_samples on success, -1 if DRED unavailable or decode failed.
|
||||
#ifdef VOICECAT_HAS_OPUS
|
||||
int decode_dred(::OpusDRED* dred, int32_t dred_offset, int16_t* out_pcm, int max_samples);
|
||||
::OpusDecoder* raw() const { return dec_; }
|
||||
#endif
|
||||
|
||||
void destroy();
|
||||
|
||||
@@ -117,11 +109,7 @@ class OpusDecoder {
|
||||
const char* error_string() const { return err_; }
|
||||
|
||||
private:
|
||||
#ifdef VOICECAT_HAS_OPUS
|
||||
::OpusDecoder* dec_ = nullptr;
|
||||
#else
|
||||
void* dec_ = nullptr;
|
||||
#endif
|
||||
int frame_samples_ = 0;
|
||||
int channels_ = 1;
|
||||
const char* err_ = nullptr;
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#include "core/client.h"
|
||||
|
||||
#ifdef VOICECAT_HAS_NET
|
||||
|
||||
#ifdef _WIN32
|
||||
# ifndef WIN32_LEAN_AND_MEAN
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
@@ -1867,7 +1865,6 @@ vc_result vc_client::test_inject_capture(uint32_t stream_id, const int16_t* pcm,
|
||||
}
|
||||
|
||||
vc_result vc_client::list_devices(vc_device_kind kind, vc_device_list* out) {
|
||||
#ifdef VOICECAT_HAS_AUDIO
|
||||
// Works in any connection state — device pickers need to populate pre-connect.
|
||||
auto devices = voicecat::audio::AudioEngine::enumerate_devices(kind == VC_DEVICE_INPUT);
|
||||
|
||||
@@ -1885,12 +1882,6 @@ vc_result vc_client::list_devices(vc_device_kind kind, vc_device_list* out) {
|
||||
out->items = items;
|
||||
out->count = devices.size();
|
||||
return VC_OK;
|
||||
#else
|
||||
(void)kind;
|
||||
out->items = nullptr;
|
||||
out->count = 0;
|
||||
return VC_ERR_NOT_IMPLEMENTED;
|
||||
#endif
|
||||
}
|
||||
|
||||
// ── M4: channel/user/stream snapshot getters ─────────────────────────────────
|
||||
@@ -2209,102 +2200,3 @@ void vc_client::run_talk_timer() {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(kTalkPollMs));
|
||||
}
|
||||
}
|
||||
|
||||
#else // !VOICECAT_HAS_NET
|
||||
|
||||
// ── M0 stub implementations ───────────────────────────────────────────────────
|
||||
|
||||
vc_client::vc_client(const vc_config& cfg, vc_callbacks cb) : cfg_(cfg), cb_(cb) {}
|
||||
vc_client::~vc_client() = default;
|
||||
|
||||
void vc_client::emit(const vc_event& ev) const {
|
||||
if (cb_.on_event) cb_.on_event(cb_.user, &ev);
|
||||
}
|
||||
|
||||
vc_result vc_client::connect(const char*, uint16_t) { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::disconnect() { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::authenticate_guest(const char*) { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::authenticate_user(const char*, const char*) { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::join_channel(uint32_t, const char*) { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::leave_channel() { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::join_voice() { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::leave_voice() { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::stream_start(const vc_stream_desc&, uint32_t*) { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::stream_stop(uint32_t) { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::set_input_device(uint32_t, const char*) { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::set_capture_channels(uint32_t, uint32_t) { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::set_input_mode(vc_input_mode) { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::set_vad_threshold(float) { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::set_input_gain(float) { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::set_input_noise_reduction(bool) { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::set_push_to_talk(bool) { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::set_self_mute(bool, bool) { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::set_remote_stream(uint32_t, uint32_t, float, bool, bool) {
|
||||
return VC_ERR_NOT_IMPLEMENTED;
|
||||
}
|
||||
vc_result vc_client::get_remote_stream(uint32_t, uint32_t, vc_remote_stream_state*) {
|
||||
return VC_ERR_NOT_IMPLEMENTED;
|
||||
}
|
||||
vc_result vc_client::send_text(vc_text_scope, uint32_t, const char*) { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::list_devices(vc_device_kind, vc_device_list* out) {
|
||||
out->items = nullptr;
|
||||
out->count = 0;
|
||||
return VC_ERR_NOT_IMPLEMENTED;
|
||||
}
|
||||
vc_result vc_client::get_stream_audio_config(uint32_t, uint32_t, vc_audio_config*) {
|
||||
return VC_ERR_NOT_IMPLEMENTED;
|
||||
}
|
||||
vc_result vc_client::stream_feed_pcm(uint32_t, const int16_t*, size_t, uint32_t) {
|
||||
return VC_ERR_NOT_IMPLEMENTED;
|
||||
}
|
||||
vc_result vc_client::set_pcm_sink(vc_pcm_sink_cb, void*) {
|
||||
return VC_ERR_NOT_IMPLEMENTED;
|
||||
}
|
||||
vc_result vc_client::set_mixed_output_sink(vc_mixed_output_cb, void*) {
|
||||
return VC_ERR_NOT_IMPLEMENTED;
|
||||
}
|
||||
vc_result vc_client::set_external_playback(bool) {
|
||||
return VC_ERR_NOT_IMPLEMENTED;
|
||||
}
|
||||
vc_result vc_client::test_inject_capture(uint32_t, const int16_t*, size_t) {
|
||||
return VC_ERR_NOT_IMPLEMENTED;
|
||||
}
|
||||
vc_result vc_client::list_channels(vc_channel_list* out) {
|
||||
out->items = nullptr;
|
||||
out->count = 0;
|
||||
return VC_ERR_NOT_IMPLEMENTED;
|
||||
}
|
||||
vc_result vc_client::list_users(vc_user_list* out) {
|
||||
out->items = nullptr;
|
||||
out->count = 0;
|
||||
return VC_ERR_NOT_IMPLEMENTED;
|
||||
}
|
||||
vc_result vc_client::list_user_streams(uint32_t, vc_stream_summary_list* out) {
|
||||
out->items = nullptr;
|
||||
out->count = 0;
|
||||
return VC_ERR_NOT_IMPLEMENTED;
|
||||
}
|
||||
vc_result vc_client::confirm_server_identity(bool) { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::get_server_identity_display(char*, size_t, size_t* out_len) {
|
||||
if (out_len) *out_len = 0;
|
||||
return VC_ERR_NOT_IMPLEMENTED;
|
||||
}
|
||||
vc_result vc_client::kick_user(uint32_t, const char*) { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::ban_user(uint32_t, const char*, uint64_t) { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::set_permission(uint32_t, const vc_permissions*) { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::set_server_mute(uint32_t, bool, bool) { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::move_user(uint32_t, uint32_t) { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::create_channel(const vc_channel_info*) { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::edit_channel(const vc_channel_info*) { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::delete_channel(uint32_t) { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::create_account(const char*, const char*) { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::reset_password(const char*, const char*) { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::delete_account(const char*) { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::list_accounts() { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::get_account_list(vc_account_list*) { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::get_permissions(vc_permissions*) { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::audio_suspend() { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::audio_resume() { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::audio_restart() { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
|
||||
#endif // VOICECAT_HAS_NET
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
|
||||
#include "voicecat.h"
|
||||
|
||||
#ifdef VOICECAT_HAS_NET
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
@@ -29,8 +27,6 @@
|
||||
#include "session/session.h"
|
||||
#include "proto/voicecat.pb.h"
|
||||
|
||||
#endif // VOICECAT_HAS_NET
|
||||
|
||||
struct vc_client {
|
||||
vc_client(const vc_config& cfg, vc_callbacks cb);
|
||||
~vc_client();
|
||||
@@ -118,11 +114,7 @@ struct vc_client {
|
||||
vc_result get_permissions(vc_permissions* out);
|
||||
|
||||
vc_connection_state state() const {
|
||||
#ifdef VOICECAT_HAS_NET
|
||||
return state_net_.load(std::memory_order_acquire);
|
||||
#else
|
||||
return state_;
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -131,7 +123,6 @@ struct vc_client {
|
||||
vc_config cfg_{};
|
||||
vc_callbacks cb_{};
|
||||
|
||||
#ifdef VOICECAT_HAS_NET
|
||||
// ── M1: TCP/TLS control channel ─────────────────────────────────────────────
|
||||
std::atomic<vc_connection_state> state_net_{VC_STATE_DISCONNECTED};
|
||||
|
||||
@@ -339,12 +330,8 @@ struct vc_client {
|
||||
// capture callback never allocates or races this pointer.
|
||||
std::unique_ptr<voicecat::audio::ApmProcessor> mic_ns_;
|
||||
|
||||
// teardown_voice() is called both from run_io()'s own cleanup (on the io_thread_, when
|
||||
// the read loop exits) and from disconnect() (on the caller's thread) -- without
|
||||
// serializing those two call sites, both can see udp_thread_/talk_timer_thread_ as
|
||||
// joinable() at the same time and race to join() the same std::thread object (UB; an
|
||||
// intermittent "No such process" std::system_error on Windows is the typical symptom).
|
||||
// This mutex makes teardown_voice() idempotent under concurrent calls.
|
||||
// teardown_voice() is called from run_io() and disconnect() concurrently; this mutex
|
||||
// makes it idempotent (avoids a double-join race on udp_thread_/talk_timer_thread_).
|
||||
std::mutex teardown_mu_;
|
||||
|
||||
// ── io_thread_ entry point ──────────────────────────────────────────────────
|
||||
@@ -432,10 +419,6 @@ struct vc_client {
|
||||
// Convenience event emitters.
|
||||
void emit_error(vc_result r, const char* text);
|
||||
void emit_disconnected(vc_result r, const char* reason);
|
||||
|
||||
#else // !VOICECAT_HAS_NET
|
||||
vc_connection_state state_{VC_STATE_DISCONNECTED};
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // VOICECAT_CORE_CLIENT_H
|
||||
|
||||
@@ -4,13 +4,10 @@
|
||||
* Used for Argon2id password hashing (deliberately slow) and TLS handshakes so
|
||||
* neither blocks the net thread. Real-time audio threads never use this.
|
||||
*
|
||||
* Requires VOICECAT_HAS_NET (Asio). Undefined when building without deps.
|
||||
*/
|
||||
#ifndef VOICECAT_CORE_WORKER_POOL_H
|
||||
#define VOICECAT_CORE_WORKER_POOL_H
|
||||
|
||||
#ifdef VOICECAT_HAS_NET
|
||||
|
||||
#include <asio/thread_pool.hpp>
|
||||
#include <asio/post.hpp>
|
||||
#include <cstddef>
|
||||
@@ -37,5 +34,4 @@ class WorkerPool {
|
||||
|
||||
} // namespace voicecat
|
||||
|
||||
#endif // VOICECAT_HAS_NET
|
||||
#endif // VOICECAT_CORE_WORKER_POOL_H
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#include "crypto/crypto.h"
|
||||
|
||||
#ifdef VOICECAT_HAS_NET
|
||||
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
@@ -406,5 +404,3 @@ long SodiumMediaCrypto::open(const uint8_t* sealed, size_t len, const uint8_t* a
|
||||
}
|
||||
|
||||
} // namespace voicecat::crypto
|
||||
|
||||
#endif // VOICECAT_HAS_NET
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#ifdef VOICECAT_HAS_NET
|
||||
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
@@ -186,25 +184,4 @@ class SodiumMediaCrypto final : public MediaCrypto {
|
||||
|
||||
} // namespace voicecat::crypto
|
||||
|
||||
#else // !VOICECAT_HAS_NET — skeleton stubs
|
||||
|
||||
namespace voicecat::crypto {
|
||||
|
||||
class MediaCrypto {
|
||||
public:
|
||||
virtual ~MediaCrypto() = default;
|
||||
virtual long seal(const uint8_t*, size_t, const uint8_t*, size_t, uint8_t*, size_t) = 0;
|
||||
virtual long open(const uint8_t*, size_t, const uint8_t*, size_t, uint8_t*, size_t) = 0;
|
||||
};
|
||||
|
||||
class SodiumMediaCrypto final : public MediaCrypto {
|
||||
public:
|
||||
long seal(const uint8_t*, size_t, const uint8_t*, size_t, uint8_t*, size_t) override { return -1; }
|
||||
long open(const uint8_t*, size_t, const uint8_t*, size_t, uint8_t*, size_t) override { return -1; }
|
||||
uint64_t peek_send_counter() const { return 0; }
|
||||
};
|
||||
|
||||
} // namespace voicecat::crypto
|
||||
|
||||
#endif // VOICECAT_HAS_NET
|
||||
#endif // VOICECAT_CRYPTO_CRYPTO_H
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#include "crypto/tofu_store.h"
|
||||
|
||||
#ifdef VOICECAT_HAS_NET
|
||||
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
@@ -93,5 +91,3 @@ void TofuStore::save() const {
|
||||
}
|
||||
|
||||
} // namespace voicecat::crypto
|
||||
|
||||
#endif // VOICECAT_HAS_NET
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
#ifndef VOICECAT_CRYPTO_TOFU_STORE_H
|
||||
#define VOICECAT_CRYPTO_TOFU_STORE_H
|
||||
|
||||
#ifdef VOICECAT_HAS_NET
|
||||
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <mutex>
|
||||
@@ -62,5 +60,4 @@ class TofuStore {
|
||||
|
||||
} // namespace voicecat::crypto
|
||||
|
||||
#endif // VOICECAT_HAS_NET
|
||||
#endif // VOICECAT_CRYPTO_TOFU_STORE_H
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#include "net/transport.h"
|
||||
|
||||
#ifdef VOICECAT_HAS_NET
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "crypto/crypto.h"
|
||||
@@ -483,5 +481,3 @@ asio::ip::udp::endpoint UdpMediaChannel::local_endpoint() const {
|
||||
}
|
||||
|
||||
} // namespace voicecat::net
|
||||
|
||||
#endif // VOICECAT_HAS_NET
|
||||
|
||||
@@ -4,9 +4,8 @@
|
||||
* Design: docs/architecture.md (Net thread), docs/protocol.md §1 (framing).
|
||||
* Implementation uses standalone Asio for sockets and timers.
|
||||
*
|
||||
* The real classes are compiled only when VOICECAT_HAS_NET is defined (dev/release/
|
||||
* server-release — vcpkg deps on). The skeleton-preset stub definitions below keep the
|
||||
* no-deps build green.
|
||||
* Design: docs/architecture.md (Net thread), docs/protocol.md §1 (framing).
|
||||
* Implementation uses standalone Asio for sockets and timers.
|
||||
*/
|
||||
#ifndef VOICECAT_NET_TRANSPORT_H
|
||||
#define VOICECAT_NET_TRANSPORT_H
|
||||
@@ -14,8 +13,6 @@
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#ifdef VOICECAT_HAS_NET
|
||||
|
||||
#define ASIO_STANDALONE 1
|
||||
#include <asio.hpp>
|
||||
|
||||
@@ -226,21 +223,4 @@ class UdpMediaChannel {
|
||||
|
||||
} // namespace voicecat::net
|
||||
|
||||
#else // !VOICECAT_HAS_NET — skeleton stubs for the dev preset
|
||||
|
||||
namespace voicecat::net {
|
||||
|
||||
class TcpControlChannel {
|
||||
public:
|
||||
bool connected() const { return false; }
|
||||
};
|
||||
|
||||
class UdpMediaChannel {
|
||||
public:
|
||||
bool bound() const { return false; }
|
||||
};
|
||||
|
||||
} // namespace voicecat::net
|
||||
|
||||
#endif // VOICECAT_HAS_NET
|
||||
#endif // VOICECAT_NET_TRANSPORT_H
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#include "protocol/envelope.h"
|
||||
|
||||
#ifdef VOICECAT_HAS_NET
|
||||
|
||||
#include "protocol/protocol.h"
|
||||
|
||||
#include <atomic>
|
||||
@@ -30,5 +28,3 @@ uint64_t next_request_id() {
|
||||
}
|
||||
|
||||
} // namespace voicecat::protocol
|
||||
|
||||
#endif // VOICECAT_HAS_NET
|
||||
|
||||
@@ -5,13 +5,10 @@
|
||||
* envelopes without touching proto types directly. All callers that do need
|
||||
* the proto types can #include the generated header alongside this one.
|
||||
*
|
||||
* Requires VOICECAT_HAS_NET (protobuf codegen).
|
||||
*/
|
||||
#ifndef VOICECAT_PROTOCOL_ENVELOPE_H
|
||||
#define VOICECAT_PROTOCOL_ENVELOPE_H
|
||||
|
||||
#ifdef VOICECAT_HAS_NET
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
@@ -35,5 +32,4 @@ uint64_t next_request_id();
|
||||
|
||||
} // namespace voicecat::protocol
|
||||
|
||||
#endif // VOICECAT_HAS_NET
|
||||
#endif // VOICECAT_PROTOCOL_ENVELOPE_H
|
||||
|
||||
@@ -23,8 +23,6 @@ std::pair<const User*, const Stream*> SessionModel::find_user_by_ssrc(uint32_t s
|
||||
return {nullptr, nullptr};
|
||||
}
|
||||
|
||||
#ifdef VOICECAT_HAS_NET
|
||||
|
||||
namespace {
|
||||
|
||||
void copy_channel_audio(Channel& ch, const voicecat::v1::AudioConfig& a) {
|
||||
@@ -160,6 +158,4 @@ void SessionModel::apply_channel_event(const voicecat::v1::ChannelEvent& ev) {
|
||||
}
|
||||
}
|
||||
|
||||
#endif // VOICECAT_HAS_NET
|
||||
|
||||
} // namespace voicecat::session
|
||||
|
||||
@@ -12,9 +12,7 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#ifdef VOICECAT_HAS_NET
|
||||
#include "proto/voicecat.pb.h"
|
||||
#endif
|
||||
|
||||
namespace voicecat::session {
|
||||
|
||||
@@ -88,11 +86,9 @@ class SessionModel {
|
||||
// Returns {nullptr, nullptr} if not found.
|
||||
std::pair<const User*, const Stream*> find_user_by_ssrc(uint32_t ssrc) const;
|
||||
|
||||
#ifdef VOICECAT_HAS_NET
|
||||
void apply_snapshot(const voicecat::v1::ServerStateSnapshot& snap);
|
||||
void apply_user_event(const voicecat::v1::UserEvent& ev);
|
||||
void apply_channel_event(const voicecat::v1::ChannelEvent& ev);
|
||||
#endif
|
||||
|
||||
private:
|
||||
std::vector<Channel> channels_;
|
||||
|
||||
Reference in New Issue
Block a user