Add encrypted managed UDP relay and native voice conformance
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <csignal>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
@@ -13,6 +14,7 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "voicecat.h"
|
||||
|
||||
@@ -24,6 +26,11 @@ void on_sigint(int) { g_stop.store(true); }
|
||||
struct Stats {
|
||||
std::atomic<bool> auth_done{false};
|
||||
std::atomic<bool> auth_ok{false};
|
||||
std::atomic<uint32_t> self_id{0};
|
||||
std::atomic<bool> voice_subscribed{false};
|
||||
std::atomic<bool> own_stream_ready{false};
|
||||
std::atomic<int> pcm_frames{0};
|
||||
std::atomic<long long> pcm_energy{0};
|
||||
// Set right after vc_client_create, before vc_connect — lets on_event auto-confirm the
|
||||
// TOFU gate (VC_EVENT_SERVER_IDENTITY below). vccli has no interactive prompt, so it
|
||||
// trusts-on-first-connect unconditionally (prints the fingerprint for visibility).
|
||||
@@ -52,6 +59,7 @@ void on_event(void* user, const vc_event* ev) {
|
||||
vc_confirm_server_identity(st->client, 1);
|
||||
break;
|
||||
case VC_EVENT_AUTH_RESULT:
|
||||
st->self_id = ev->user_id;
|
||||
st->auth_ok = (ev->result == VC_OK);
|
||||
st->auth_done = true;
|
||||
std::printf("[auth] ok=%d user_id=%u %s\n", st->auth_ok.load(), ev->user_id,
|
||||
@@ -71,9 +79,13 @@ void on_event(void* user, const vc_event* ev) {
|
||||
std::printf("[user] updated: id=%u\n", ev->user_id);
|
||||
break;
|
||||
case VC_EVENT_STREAM_STARTED:
|
||||
if (ev->user_id == st->self_id.load()) st->own_stream_ready = true;
|
||||
std::printf("[voice] stream started: user_id=%u stream_id=%u\n", ev->user_id,
|
||||
ev->stream_id);
|
||||
break;
|
||||
case VC_EVENT_VOICE_STATE:
|
||||
st->voice_subscribed = ev->u32a == 1;
|
||||
break;
|
||||
case VC_EVENT_STREAM_STOPPED:
|
||||
std::printf("[voice] stream stopped: user_id=%u stream_id=%u\n", ev->user_id,
|
||||
ev->stream_id);
|
||||
@@ -124,6 +136,16 @@ bool wait_until(std::atomic<bool>& flag, int timeout_ms) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void test_pcm_sink(void* context, uint32_t user, uint32_t, const int16_t* pcm,
|
||||
size_t samples, uint32_t channels, uint32_t rate) {
|
||||
auto& stats = *static_cast<Stats*>(context);
|
||||
if (user == stats.self_id.load() || rate != 48000) return;
|
||||
long long energy = 0;
|
||||
for (size_t index = 0; index < samples * channels; ++index) energy += std::abs(static_cast<int>(pcm[index]));
|
||||
stats.pcm_energy.fetch_add(energy);
|
||||
stats.pcm_frames.fetch_add(1);
|
||||
}
|
||||
|
||||
vc_result wait_generic_result(Stats& st, int baseline_count, int timeout_ms) {
|
||||
auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeout_ms);
|
||||
while (st.generic_result_count.load() <= baseline_count) {
|
||||
@@ -202,6 +224,7 @@ void print_usage() {
|
||||
"\n"
|
||||
"Voice / devices:\n"
|
||||
" --voice start a MIC stream and stay connected until Ctrl+C\n"
|
||||
" --test-tone-ms MS finite headless voice test (500..60000 ms); requires a peer\n"
|
||||
" --mute start with the mic muted (only meaningful with --voice)\n"
|
||||
" --list-devices print input/output devices (vc_list_devices) and exit\n"
|
||||
" --input-device ID use device ID (from --list-devices) for the MIC stream\n"
|
||||
@@ -309,6 +332,7 @@ int main(int argc, char** argv) {
|
||||
bool have_password = false;
|
||||
uint32_t channel_id = 1;
|
||||
bool voice_mode = false;
|
||||
uint32_t test_tone_ms = 0;
|
||||
bool start_muted = false;
|
||||
bool self_mute = false;
|
||||
bool self_deafen = false;
|
||||
@@ -370,6 +394,12 @@ int main(int argc, char** argv) {
|
||||
else if (a == "--password") { password = next(); have_password = true; }
|
||||
else if (a == "--channel") { if (!parse_u32(next().c_str(), &channel_id, "--channel")) return 1; }
|
||||
else if (a == "--voice") voice_mode = true;
|
||||
else if (a == "--test-tone-ms") {
|
||||
if (!parse_u32(next().c_str(), &test_tone_ms, "--test-tone-ms") || test_tone_ms < 500 || test_tone_ms > 60000) {
|
||||
std::fprintf(stderr, "--test-tone-ms must be between 500 and 60000\n"); return 1;
|
||||
}
|
||||
voice_mode = true;
|
||||
}
|
||||
else if (a == "--mute") start_muted = true;
|
||||
else if (a == "--self-mute") self_mute = true;
|
||||
else if (a == "--self-deafen") self_deafen = true;
|
||||
@@ -441,6 +471,9 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
|
||||
// Validate auth mode.
|
||||
if (test_tone_ms && (start_muted || self_mute || self_deafen || share_screen_audio || have_input_device)) {
|
||||
std::fprintf(stderr, "--test-tone-ms cannot be combined with mute, deafen or device capture\n"); return 1;
|
||||
}
|
||||
if (have_username != have_password) {
|
||||
std::fprintf(stderr, "--username and --password must be used together\n");
|
||||
return 1;
|
||||
@@ -490,6 +523,10 @@ int main(int argc, char** argv) {
|
||||
return 1;
|
||||
}
|
||||
st.client = c;
|
||||
if (test_tone_ms) {
|
||||
vc_set_external_playback(c, 1);
|
||||
vc_set_pcm_sink(c, test_pcm_sink, &st);
|
||||
}
|
||||
|
||||
if (list_devices) {
|
||||
// Device enumeration works pre-connect (no server needed) — see docs/voice.md.
|
||||
@@ -641,26 +678,54 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
|
||||
if (voice_mode) {
|
||||
r = vc_join_voice(c);
|
||||
if (r != VC_OK || !wait_until(st.voice_subscribed, 8000)) {
|
||||
std::fprintf(stderr, "voice subscription failed or timed out\n");
|
||||
vc_disconnect(c); vc_client_destroy(c); return 1;
|
||||
}
|
||||
// Give the async UDP binding handshake (TCP UdpBinding -> ack -> plaintext
|
||||
// bootstrap packet) a moment to land before announcing a stream.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
|
||||
if (start_muted) vc_set_self_mute(c, 1, 0);
|
||||
|
||||
r = vc_set_input_mode(c, input_mode);
|
||||
vc_input_mode effective_input_mode = test_tone_ms ? VC_INPUT_ALWAYS_ON : input_mode;
|
||||
r = vc_set_input_mode(c, effective_input_mode);
|
||||
std::printf("vc_set_input_mode(%s) -> %d (%s)\n",
|
||||
input_mode == VC_INPUT_PUSH_TO_TALK ? "ptt" : "vad", r, vc_result_string(r));
|
||||
effective_input_mode == VC_INPUT_ALWAYS_ON ? "always-on" : input_mode == VC_INPUT_PUSH_TO_TALK ? "ptt" : "vad", r, vc_result_string(r));
|
||||
|
||||
vc_stream_desc desc{};
|
||||
desc.kind = VC_STREAM_MIC;
|
||||
desc.device_id = have_input_device ? input_device.c_str() : nullptr;
|
||||
desc.label = "Microphone";
|
||||
desc.external_feed = test_tone_ms ? 1 : 0;
|
||||
|
||||
uint32_t stream_id = 0;
|
||||
r = vc_stream_start(c, &desc, &stream_id);
|
||||
std::printf("vc_stream_start -> %d (%s), stream_id=%u\n", r, vc_result_string(r),
|
||||
stream_id);
|
||||
|
||||
if (test_tone_ms) {
|
||||
vc_audio_config audio{};
|
||||
bool ok = r == VC_OK && wait_until(st.own_stream_ready, 8000) &&
|
||||
vc_get_stream_audio_config(c, st.self_id.load(), stream_id, &audio) == VC_OK;
|
||||
uint32_t channels = audio.mode == 1 ? 2 : 1;
|
||||
std::vector<int16_t> pcm(960 * channels);
|
||||
for (size_t sample = 0; sample < 960; ++sample)
|
||||
for (uint32_t side = 0; side < channels; ++side)
|
||||
pcm[sample * channels + side] = static_cast<int16_t>(12000 * std::sin(sample * (side ? 0.083 : 0.058)));
|
||||
auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(test_tone_ms);
|
||||
while (ok && !g_stop.load() && std::chrono::steady_clock::now() < deadline) {
|
||||
ok = vc_stream_feed_pcm(c, stream_id, pcm.data(), 960, channels) == VC_OK;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
}
|
||||
vc_stream_stop(c, stream_id);
|
||||
vc_disconnect(c);
|
||||
vc_client_destroy(c);
|
||||
std::printf("[test-tone] received=%d energy=%lld channels=%u\n", st.pcm_frames.load(), st.pcm_energy.load(), channels);
|
||||
return ok && st.pcm_frames.load() >= 5 && st.pcm_energy.load() > 0 && !mod_request_error ? 0 : 1;
|
||||
}
|
||||
|
||||
if (have_input_device && r == VC_OK) {
|
||||
r = vc_set_input_device(c, stream_id, input_device.c_str());
|
||||
std::printf("vc_set_input_device -> %d (%s)\n", r, vc_result_string(r));
|
||||
|
||||
Reference in New Issue
Block a user