scaffold: M0 skeleton + agent onboarding (build, architecture, progress)
Turn the design into a buildable, dependency-free M0 skeleton plus the
onboarding layer so a new agent can pick up instantly.
Build system:
- CMake + CMakePresets (dev = no deps; server-release = vcpkg) + vcpkg.json
- Skeleton builds with just a C++20 compiler; deps stay off until needed
- .gitattributes (LF), .gitignore, .clang-format
Core (libvoicecat):
- core/include/voicecat.h: full C ABI (the client/server contract), stubbed
- core/proto/voicecat.proto: control-plane wire format, matches docs/protocol.md
- src/{net,crypto,codec,protocol,session,audio,core}: subsystem stubs that
return VC_ERR_NOT_IMPLEMENTED, each pointing to its design doc
- server/ (voicecat-server) and tools/vccli/ link the core
- tests/: CTest smoke test asserting the C ABI contract (behavior, not just build)
- clients/{apple,windows}: M4 placeholders
Onboarding for agents:
- CLAUDE.md: hub — build/test commands, architecture at a glance, doc map, rules
- AGENTS.md: working method (behavior-driven; clean compile is the floor not the goal)
- PROGRESS.md: living tracker — M0 done, M1 task checklist, "where we left off"
Verified: cmake --preset dev && cmake --build --preset dev && ctest --preset dev → green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
8
core/src/audio/audio_engine.cpp
Normal file
8
core/src/audio/audio_engine.cpp
Normal file
@@ -0,0 +1,8 @@
|
||||
#include "audio/audio_engine.h"
|
||||
|
||||
namespace voicecat::audio {
|
||||
|
||||
// M0 stub. Capture/playback (miniaudio), APM DSP, jitter buffer, and mixer land in M2/M3.
|
||||
// See docs/voice.md §8–11.
|
||||
|
||||
} // namespace voicecat::audio
|
||||
40
core/src/audio/audio_engine.h
Normal file
40
core/src/audio/audio_engine.h
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* audio/audio_engine.h — capture/playback + DSP + jitter buffer + mixer.
|
||||
*
|
||||
* Design: docs/voice.md §8–11. Real-time path:
|
||||
* capture(miniaudio) → APM(AEC/NS/AGC/VAD, send-side) → Opus encode → ...
|
||||
* ... → Opus decode → per-user recv NS (listener-chosen) → gain/mute → mix → playback
|
||||
*
|
||||
* REAL-TIME RULE: audio-callback threads never allocate, lock, or block (architecture.md §3).
|
||||
*
|
||||
* STATUS: M0 stub.
|
||||
*/
|
||||
#ifndef VOICECAT_AUDIO_AUDIO_ENGINE_H
|
||||
#define VOICECAT_AUDIO_AUDIO_ENGINE_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace voicecat::audio {
|
||||
|
||||
// Adaptive per-ssrc jitter buffer (voice.md §5). TODO(M2).
|
||||
class JitterBuffer {
|
||||
public:
|
||||
uint32_t target_depth_ms() const { return target_depth_ms_; }
|
||||
|
||||
private:
|
||||
uint32_t target_depth_ms_ = 40;
|
||||
};
|
||||
|
||||
// Owns miniaudio capture/playback, the APM instances, codecs, jitter buffers, and the mixer.
|
||||
class AudioEngine {
|
||||
public:
|
||||
// TODO(M2): start/stop capture+playback; push/pull frames via lock-free ring buffers.
|
||||
bool running() const { return running_; }
|
||||
|
||||
private:
|
||||
bool running_ = false;
|
||||
};
|
||||
|
||||
} // namespace voicecat::audio
|
||||
|
||||
#endif // VOICECAT_AUDIO_AUDIO_ENGINE_H
|
||||
7
core/src/codec/opus_codec.cpp
Normal file
7
core/src/codec/opus_codec.cpp
Normal file
@@ -0,0 +1,7 @@
|
||||
#include "codec/opus_codec.h"
|
||||
|
||||
namespace voicecat::codec {
|
||||
|
||||
// M0 stub. Brought up in M2. See docs/voice.md §3–4.
|
||||
|
||||
} // namespace voicecat::codec
|
||||
39
core/src/codec/opus_codec.h
Normal file
39
core/src/codec/opus_codec.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* codec/opus_codec.h — Opus encode/decode (libopus 1.6).
|
||||
*
|
||||
* Design: docs/voice.md §3–4. Per-channel AudioConfig (mono/stereo, bitrate, frame size,
|
||||
* FEC, DTX, complexity). The server relays Opus payloads unmodified (no transcode).
|
||||
*
|
||||
* STATUS: M0 stub.
|
||||
*/
|
||||
#ifndef VOICECAT_CODEC_OPUS_CODEC_H
|
||||
#define VOICECAT_CODEC_OPUS_CODEC_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace voicecat::codec {
|
||||
|
||||
struct OpusParams {
|
||||
uint32_t sample_rate = 48000;
|
||||
uint32_t bitrate_bps = 24000;
|
||||
uint32_t frame_ms = 20;
|
||||
bool stereo = false;
|
||||
bool fec = true;
|
||||
bool dtx = true;
|
||||
uint32_t complexity = 10;
|
||||
uint32_t expected_packet_loss = 0;
|
||||
};
|
||||
|
||||
class OpusEncoder {
|
||||
public:
|
||||
// TODO(M2): init(params); encode(pcm, frame) -> opus bytes.
|
||||
};
|
||||
|
||||
class OpusDecoder {
|
||||
public:
|
||||
// TODO(M2): init(params); decode(opus, out_pcm); PLC on loss; FEC from next packet.
|
||||
};
|
||||
|
||||
} // namespace voicecat::codec
|
||||
|
||||
#endif // VOICECAT_CODEC_OPUS_CODEC_H
|
||||
48
core/src/core/client.cpp
Normal file
48
core/src/core/client.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#include "core/client.h"
|
||||
|
||||
namespace {
|
||||
constexpr vc_result kStub = VC_ERR_NOT_IMPLEMENTED;
|
||||
} // namespace
|
||||
|
||||
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 != nullptr) {
|
||||
cb_.on_event(cb_.user, &ev);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Connection & auth ────────────────────────────────────────────────────────
|
||||
// TODO(M1): drive the TLS 1.3 control channel + handshake state machine here, updating
|
||||
// state_ and emitting VC_EVENT_CONNECTION_STATE as it advances. docs/protocol.md §4.
|
||||
vc_result vc_client::connect(const char*, uint16_t) { return kStub; }
|
||||
vc_result vc_client::disconnect() { return kStub; }
|
||||
vc_result vc_client::authenticate_guest(const char*) { return kStub; }
|
||||
vc_result vc_client::authenticate_user(const char*, const char*) { return kStub; }
|
||||
|
||||
// ── Channels ─────────────────────────────────────────────────────────────────
|
||||
vc_result vc_client::join_channel(uint32_t, const char*) { return kStub; }
|
||||
vc_result vc_client::leave_channel() { return kStub; }
|
||||
|
||||
// ── Local media streams ──────────────────────────────────────────────────────
|
||||
// TODO(M2/M3): allocate a stream id, announce it over the control channel, and start the
|
||||
// capture→APM→Opus→AEAD→UDP pipeline. docs/voice.md.
|
||||
vc_result vc_client::stream_start(const vc_stream_desc&, uint32_t*) { return kStub; }
|
||||
vc_result vc_client::stream_stop(uint32_t) { return kStub; }
|
||||
vc_result vc_client::set_input_device(uint32_t, const char*) { return kStub; }
|
||||
vc_result vc_client::set_input_mode(vc_input_mode) { return kStub; }
|
||||
vc_result vc_client::set_push_to_talk(bool) { return kStub; }
|
||||
vc_result vc_client::set_self_mute(bool, bool) { return kStub; }
|
||||
vc_result vc_client::set_remote_stream(uint32_t, uint32_t, float, bool, bool) { return kStub; }
|
||||
|
||||
// ── Text ─────────────────────────────────────────────────────────────────────
|
||||
vc_result vc_client::send_text(vc_text_scope, uint32_t, const char*) { return kStub; }
|
||||
|
||||
// ── Devices ──────────────────────────────────────────────────────────────────
|
||||
vc_result vc_client::list_devices(vc_device_kind, vc_device_list* out) {
|
||||
out->items = nullptr;
|
||||
out->count = 0;
|
||||
return kStub;
|
||||
}
|
||||
53
core/src/core/client.h
Normal file
53
core/src/core/client.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* client.h — the implementation type behind the opaque `vc_client*` handle.
|
||||
*
|
||||
* M0 skeleton: holds config/callbacks/state and returns VC_ERR_NOT_IMPLEMENTED for
|
||||
* everything that needs a subsystem. As subsystems land (docs/architecture.md §2), this
|
||||
* class wires them together: a net transport, a protocol state machine, a session model,
|
||||
* and an audio engine, plus the event thread that drains into vc_callbacks.on_event.
|
||||
*/
|
||||
#ifndef VOICECAT_CORE_CLIENT_H
|
||||
#define VOICECAT_CORE_CLIENT_H
|
||||
|
||||
#include "voicecat.h"
|
||||
|
||||
struct vc_client {
|
||||
vc_client(const vc_config& cfg, vc_callbacks cb);
|
||||
~vc_client();
|
||||
|
||||
vc_client(const vc_client&) = delete;
|
||||
vc_client& operator=(const vc_client&) = delete;
|
||||
|
||||
vc_result connect(const char* host, uint16_t port);
|
||||
vc_result disconnect();
|
||||
vc_result authenticate_guest(const char* nickname);
|
||||
vc_result authenticate_user(const char* username, const char* password);
|
||||
|
||||
vc_result join_channel(uint32_t channel_id, const char* password);
|
||||
vc_result leave_channel();
|
||||
|
||||
vc_result stream_start(const vc_stream_desc& desc, uint32_t* out_stream_id);
|
||||
vc_result stream_stop(uint32_t stream_id);
|
||||
vc_result set_input_device(uint32_t stream_id, const char* device_id);
|
||||
vc_result set_input_mode(vc_input_mode mode);
|
||||
vc_result set_push_to_talk(bool active);
|
||||
vc_result set_self_mute(bool mic_muted, bool deafened);
|
||||
vc_result set_remote_stream(uint32_t user_id, uint32_t stream_id, float gain, bool muted,
|
||||
bool noise_reduction);
|
||||
|
||||
vc_result send_text(vc_text_scope scope, uint32_t target_id, const char* utf8);
|
||||
|
||||
vc_result list_devices(vc_device_kind kind, vc_device_list* out);
|
||||
|
||||
vc_connection_state state() const { return state_; }
|
||||
|
||||
private:
|
||||
// Deliver an event to the host application. Safe to call with cb_.on_event == nullptr.
|
||||
void emit(const vc_event& ev) const;
|
||||
|
||||
vc_config cfg_{};
|
||||
vc_callbacks cb_{};
|
||||
vc_connection_state state_ = VC_STATE_DISCONNECTED;
|
||||
};
|
||||
|
||||
#endif // VOICECAT_CORE_CLIENT_H
|
||||
8
core/src/crypto/crypto.cpp
Normal file
8
core/src/crypto/crypto.cpp
Normal file
@@ -0,0 +1,8 @@
|
||||
#include "crypto/crypto.h"
|
||||
|
||||
namespace voicecat::crypto {
|
||||
|
||||
// M0 stub. Brought up in M1 (TLS 1.3 via mbedTLS) and M2 (media AEAD via libsodium).
|
||||
// See docs/security.md §1–2.
|
||||
|
||||
} // namespace voicecat::crypto
|
||||
40
core/src/crypto/crypto.h
Normal file
40
core/src/crypto/crypto.h
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* crypto/crypto.h — TLS 1.3 (mbedTLS) and the media AEAD (libsodium).
|
||||
*
|
||||
* Design: docs/security.md. Control channel = TLS 1.3. Media = keys exported from the TLS
|
||||
* session (RFC 5705 / 8446) + per-frame ChaCha20-Poly1305 with a counter nonce and a
|
||||
* sliding-window replay filter. Encryption is MANDATORY — never add a plaintext path.
|
||||
*
|
||||
* STATUS: M0 stub.
|
||||
*/
|
||||
#ifndef VOICECAT_CRYPTO_CRYPTO_H
|
||||
#define VOICECAT_CRYPTO_CRYPTO_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace voicecat::crypto {
|
||||
|
||||
// TLS 1.3 endpoint wrapper (mbedTLS). Provides the keying-material exporter that seeds
|
||||
// MediaCrypto, so the UDP path inherits the authenticated control session's trust.
|
||||
class TlsContext {
|
||||
public:
|
||||
// TODO(M1): client/server handshake; read/write; export_keying_material(label,...).
|
||||
};
|
||||
|
||||
// Per-frame media encryption. Abstracted so the backend (exported-key AEAD now; a DTLS 1.3
|
||||
// backend later, if a permissive impl matures) is swappable without touching voice code.
|
||||
class MediaCrypto {
|
||||
public:
|
||||
virtual ~MediaCrypto() = default;
|
||||
// seal/open one voice frame; `aad` carries the routable header fields (e.g. ssrc).
|
||||
// Returns bytes written, or -1 on failure (replay/auth). TODO(M2).
|
||||
virtual long seal(const uint8_t* plain, size_t len, const uint8_t* aad, size_t aad_len,
|
||||
uint8_t* out, size_t out_cap) = 0;
|
||||
virtual long open(const uint8_t* sealed, size_t len, const uint8_t* aad, size_t aad_len,
|
||||
uint8_t* out, size_t out_cap) = 0;
|
||||
};
|
||||
|
||||
} // namespace voicecat::crypto
|
||||
|
||||
#endif // VOICECAT_CRYPTO_CRYPTO_H
|
||||
8
core/src/net/transport.cpp
Normal file
8
core/src/net/transport.cpp
Normal file
@@ -0,0 +1,8 @@
|
||||
#include "net/transport.h"
|
||||
|
||||
namespace voicecat::net {
|
||||
|
||||
// M0 stub. Subsystem brought up in M1 (TCP/TLS) and M2 (UDP). See docs/protocol.md,
|
||||
// docs/voice.md, and AGENTS.md "Suggested first steps".
|
||||
|
||||
} // namespace voicecat::net
|
||||
39
core/src/net/transport.h
Normal file
39
core/src/net/transport.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* net/transport.h — TCP control channel + UDP media channel.
|
||||
*
|
||||
* Design: docs/architecture.md (Net thread), docs/protocol.md §1 (framing), docs/voice.md §2
|
||||
* (UDP frame). Implementation will use standalone Asio (one reactor) for sockets/timers.
|
||||
*
|
||||
* STATUS: M0 stub — interfaces only, no Asio yet.
|
||||
*/
|
||||
#ifndef VOICECAT_NET_TRANSPORT_H
|
||||
#define VOICECAT_NET_TRANSPORT_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace voicecat::net {
|
||||
|
||||
// Length-prefixed [u32 length][payload] framing over a TLS 1.3 byte stream (protocol.md §1).
|
||||
class TcpControlChannel {
|
||||
public:
|
||||
// TODO(M1): connect(host, port), TLS handshake, send/recv framed Envelopes.
|
||||
bool connected() const { return connected_; }
|
||||
|
||||
private:
|
||||
bool connected_ = false;
|
||||
};
|
||||
|
||||
// UDP media channel: encrypted voice frames (voice.md §2), bound to a session via token.
|
||||
class UdpMediaChannel {
|
||||
public:
|
||||
// TODO(M2): bind, send/recv AEAD-sealed voice frames, keepalive.
|
||||
bool bound() const { return bound_; }
|
||||
|
||||
private:
|
||||
bool bound_ = false;
|
||||
};
|
||||
|
||||
} // namespace voicecat::net
|
||||
|
||||
#endif // VOICECAT_NET_TRANSPORT_H
|
||||
11
core/src/protocol/protocol.cpp
Normal file
11
core/src/protocol/protocol.cpp
Normal file
@@ -0,0 +1,11 @@
|
||||
#include "protocol/protocol.h"
|
||||
|
||||
namespace voicecat::protocol {
|
||||
|
||||
// M0 stub. The frame codec + protobuf Envelope dispatch are the first M1 task
|
||||
// (AGENTS.md "Suggested first steps" #1). See docs/protocol.md §1–5.
|
||||
bool FrameCodec::feed(const uint8_t*, size_t, std::vector<std::vector<uint8_t>>&) {
|
||||
return true; // TODO(M1): real framing.
|
||||
}
|
||||
|
||||
} // namespace voicecat::protocol
|
||||
33
core/src/protocol/protocol.h
Normal file
33
core/src/protocol/protocol.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* protocol/protocol.h — control-plane (de)serialization + routing.
|
||||
*
|
||||
* Design: docs/protocol.md. Wire format is a length-prefixed protobuf `Envelope`
|
||||
* (core/proto/voicecat.proto). This layer parses frames into Envelopes, correlates
|
||||
* request_id ↔ response, and dispatches to handlers. Media frames do NOT come through here
|
||||
* (they use the fixed binary header in voice.md §2).
|
||||
*
|
||||
* STATUS: M0 stub — protobuf codegen is wired in CMake (commented) and turned on in M1.
|
||||
*/
|
||||
#ifndef VOICECAT_PROTOCOL_PROTOCOL_H
|
||||
#define VOICECAT_PROTOCOL_PROTOCOL_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace voicecat::protocol {
|
||||
|
||||
constexpr uint32_t kProtocolVersion = 1; // docs/protocol.md §4
|
||||
constexpr uint32_t kMaxFrameBytes = 16u * 1024 * 1024; // §1 oversized-frame guard
|
||||
|
||||
// Reads/writes [u32 length][payload] frames from a byte stream. TODO(M1).
|
||||
class FrameCodec {
|
||||
public:
|
||||
// Append received bytes; pop complete frame payloads. Returns false on protocol error
|
||||
// (e.g. length > kMaxFrameBytes).
|
||||
bool feed(const uint8_t* data, size_t len, std::vector<std::vector<uint8_t>>& out_frames);
|
||||
};
|
||||
|
||||
} // namespace voicecat::protocol
|
||||
|
||||
#endif // VOICECAT_PROTOCOL_PROTOCOL_H
|
||||
8
core/src/session/session.cpp
Normal file
8
core/src/session/session.cpp
Normal file
@@ -0,0 +1,8 @@
|
||||
#include "session/session.h"
|
||||
|
||||
namespace voicecat::session {
|
||||
|
||||
// M0 stub. Channel tree, users, streams, permissions, and ephemeral text relay land in M1.
|
||||
// See docs/protocol.md §5.
|
||||
|
||||
} // namespace voicecat::session
|
||||
55
core/src/session/session.h
Normal file
55
core/src/session/session.h
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* session/session.h — domain model: channels, users, streams, permissions, text.
|
||||
*
|
||||
* Design: docs/protocol.md §5, docs/architecture.md §5. Shared by client (local mirror of
|
||||
* server state) and server (authoritative). Text is ephemeral (no history). Accounts are
|
||||
* admin-provisioned.
|
||||
*
|
||||
* STATUS: M0 stub.
|
||||
*/
|
||||
#ifndef VOICECAT_SESSION_SESSION_H
|
||||
#define VOICECAT_SESSION_SESSION_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace voicecat::session {
|
||||
|
||||
struct Channel {
|
||||
uint32_t id = 0;
|
||||
uint32_t parent_id = 0;
|
||||
std::string name;
|
||||
bool password_protected = false;
|
||||
uint32_t max_users = 0;
|
||||
};
|
||||
|
||||
struct Stream {
|
||||
uint32_t stream_id = 0;
|
||||
uint32_t ssrc = 0;
|
||||
int kind = 0; // vc_stream_kind
|
||||
std::string label;
|
||||
};
|
||||
|
||||
struct User {
|
||||
uint32_t id = 0;
|
||||
std::string nickname;
|
||||
bool is_guest = true;
|
||||
uint32_t channel_id = 0;
|
||||
std::vector<Stream> streams;
|
||||
};
|
||||
|
||||
// Mirror/authority for the channel tree + user list. TODO(M1): snapshot + delta apply.
|
||||
class SessionModel {
|
||||
public:
|
||||
const std::vector<Channel>& channels() const { return channels_; }
|
||||
const std::vector<User>& users() const { return users_; }
|
||||
|
||||
private:
|
||||
std::vector<Channel> channels_;
|
||||
std::vector<User> users_;
|
||||
};
|
||||
|
||||
} // namespace voicecat::session
|
||||
|
||||
#endif // VOICECAT_SESSION_SESSION_H
|
||||
137
core/src/voicecat.cpp
Normal file
137
core/src/voicecat.cpp
Normal file
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* voicecat.cpp — C ABI implementation (M0 skeleton).
|
||||
*
|
||||
* Lifecycle (create/destroy) and trivial accessors are real. Everything that needs a
|
||||
* subsystem (net/crypto/codec/protocol/session/audio) returns VC_ERR_NOT_IMPLEMENTED for
|
||||
* now and is the work of M1+ (see AGENTS.md / docs/roadmap.md).
|
||||
*/
|
||||
#include "voicecat.h"
|
||||
|
||||
#include <new>
|
||||
|
||||
#include "core/client.h"
|
||||
|
||||
#define VC_STR2(x) #x
|
||||
#define VC_STR(x) VC_STR2(x)
|
||||
|
||||
extern "C" {
|
||||
|
||||
const char* vc_version_string(void) {
|
||||
static const char* kVersion = VC_STR(VOICECAT_VERSION_MAJOR) "." VC_STR(
|
||||
VOICECAT_VERSION_MINOR) "." VC_STR(VOICECAT_VERSION_PATCH);
|
||||
return kVersion;
|
||||
}
|
||||
|
||||
const char* vc_result_string(vc_result code) {
|
||||
switch (code) {
|
||||
case VC_OK: return "ok";
|
||||
case VC_ERR_NOT_IMPLEMENTED: return "not implemented";
|
||||
case VC_ERR_INVALID_ARG: return "invalid argument";
|
||||
case VC_ERR_NOT_CONNECTED: return "not connected";
|
||||
case VC_ERR_ALREADY: return "already in requested state";
|
||||
case VC_ERR_AUTH_FAILED: return "authentication failed";
|
||||
case VC_ERR_PERMISSION_DENIED: return "permission denied";
|
||||
case VC_ERR_TIMEOUT: return "timeout";
|
||||
case VC_ERR_IO: return "i/o error";
|
||||
case VC_ERR_PROTOCOL: return "protocol error";
|
||||
case VC_ERR_CRYPTO: return "crypto error";
|
||||
case VC_ERR_AUDIO: return "audio error";
|
||||
case VC_ERR_INTERNAL: return "internal error";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
vc_client* vc_client_create(const vc_config* cfg, vc_callbacks cb) {
|
||||
if (cfg == nullptr) return nullptr;
|
||||
return new (std::nothrow) vc_client(*cfg, cb);
|
||||
}
|
||||
|
||||
void vc_client_destroy(vc_client* c) { delete c; }
|
||||
|
||||
/* ── Everything below delegates to the (stub) client. ─────────────────────── */
|
||||
|
||||
vc_result vc_connect(vc_client* c, const char* host, uint16_t port) {
|
||||
if (c == nullptr || host == nullptr) return VC_ERR_INVALID_ARG;
|
||||
return c->connect(host, port);
|
||||
}
|
||||
|
||||
vc_result vc_disconnect(vc_client* c) {
|
||||
if (c == nullptr) return VC_ERR_INVALID_ARG;
|
||||
return c->disconnect();
|
||||
}
|
||||
|
||||
vc_result vc_authenticate_guest(vc_client* c, const char* nickname) {
|
||||
if (c == nullptr || nickname == nullptr) return VC_ERR_INVALID_ARG;
|
||||
return c->authenticate_guest(nickname);
|
||||
}
|
||||
|
||||
vc_result vc_authenticate_user(vc_client* c, const char* username, const char* password) {
|
||||
if (c == nullptr || username == nullptr || password == nullptr) return VC_ERR_INVALID_ARG;
|
||||
return c->authenticate_user(username, password);
|
||||
}
|
||||
|
||||
vc_result vc_join_channel(vc_client* c, uint32_t channel_id, const char* password) {
|
||||
if (c == nullptr) return VC_ERR_INVALID_ARG;
|
||||
return c->join_channel(channel_id, password);
|
||||
}
|
||||
|
||||
vc_result vc_leave_channel(vc_client* c) {
|
||||
if (c == nullptr) return VC_ERR_INVALID_ARG;
|
||||
return c->leave_channel();
|
||||
}
|
||||
|
||||
vc_result vc_stream_start(vc_client* c, const vc_stream_desc* desc, uint32_t* out_stream_id) {
|
||||
if (c == nullptr || desc == nullptr) return VC_ERR_INVALID_ARG;
|
||||
return c->stream_start(*desc, out_stream_id);
|
||||
}
|
||||
|
||||
vc_result vc_stream_stop(vc_client* c, uint32_t stream_id) {
|
||||
if (c == nullptr) return VC_ERR_INVALID_ARG;
|
||||
return c->stream_stop(stream_id);
|
||||
}
|
||||
|
||||
vc_result vc_set_input_device(vc_client* c, uint32_t stream_id, const char* device_id) {
|
||||
if (c == nullptr) return VC_ERR_INVALID_ARG;
|
||||
return c->set_input_device(stream_id, device_id);
|
||||
}
|
||||
|
||||
vc_result vc_set_input_mode(vc_client* c, vc_input_mode mode) {
|
||||
if (c == nullptr) return VC_ERR_INVALID_ARG;
|
||||
return c->set_input_mode(mode);
|
||||
}
|
||||
|
||||
vc_result vc_set_push_to_talk(vc_client* c, int active) {
|
||||
if (c == nullptr) return VC_ERR_INVALID_ARG;
|
||||
return c->set_push_to_talk(active != 0);
|
||||
}
|
||||
|
||||
vc_result vc_set_self_mute(vc_client* c, int mic_muted, int deafened) {
|
||||
if (c == nullptr) return VC_ERR_INVALID_ARG;
|
||||
return c->set_self_mute(mic_muted != 0, deafened != 0);
|
||||
}
|
||||
|
||||
vc_result vc_set_remote_stream(vc_client* c, uint32_t user_id, uint32_t stream_id, float gain,
|
||||
int muted, int noise_reduction) {
|
||||
if (c == nullptr) return VC_ERR_INVALID_ARG;
|
||||
return c->set_remote_stream(user_id, stream_id, gain, muted != 0, noise_reduction != 0);
|
||||
}
|
||||
|
||||
vc_result vc_send_text(vc_client* c, vc_text_scope scope, uint32_t target_id,
|
||||
const char* utf8) {
|
||||
if (c == nullptr || utf8 == nullptr) return VC_ERR_INVALID_ARG;
|
||||
return c->send_text(scope, target_id, utf8);
|
||||
}
|
||||
|
||||
vc_result vc_list_devices(vc_client* c, vc_device_kind kind, vc_device_list* out) {
|
||||
if (c == nullptr || out == nullptr) return VC_ERR_INVALID_ARG;
|
||||
return c->list_devices(kind, out);
|
||||
}
|
||||
|
||||
void vc_free_device_list(vc_device_list* list) {
|
||||
if (list == nullptr) return;
|
||||
/* Stub: no allocation yet. Real impl frees list->items here. */
|
||||
list->items = nullptr;
|
||||
list->count = 0;
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
Reference in New Issue
Block a user