fix(M2): wire vc_client's real voice plane through the C ABI, not just raw sockets
test_m2_voice passed against raw BSD sockets, but vc_client::stream_start/stop, UDP binding, and capture/recv were still VC_ERR_NOT_IMPLEMENTED stubs -- meaning vccli and any GUI client still couldn't actually talk. Implements the real client-side UDP-binding handshake, media key derivation, capture->encode->seal-> send and recv->open->decode->playback paths, plus server-side StreamInfo broadcast so peers learn about each other's streams via sync_remote_streams(). Adds test_voice_client_abi (two real vc_client instances, not raw sockets) and vccli --voice/--mute/--text flags, manually verified live between two instances. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -82,6 +82,10 @@ void ConnSession::on_frame(std::vector<uint8_t> frame) {
|
||||
if (st == State::Authenticated)
|
||||
handle_stream_announce(env.request_id(), env.stream_announce());
|
||||
break;
|
||||
case voicecat::v1::Envelope::kStreamStop:
|
||||
if (st == State::Authenticated)
|
||||
handle_stream_stop(env.stream_stop());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -346,7 +350,37 @@ void ConnSession::handle_stream_announce(uint64_t req_id,
|
||||
if (eff->bitrate_bps() == 0) eff->set_bitrate_bps(24000);
|
||||
if (eff->frame_ms() == 0) eff->set_frame_ms(20);
|
||||
|
||||
announced_stream_id_ = res->stream_id();
|
||||
|
||||
voicecat::v1::StreamInfo info;
|
||||
info.set_stream_id(res->stream_id());
|
||||
info.set_ssrc(ssrc);
|
||||
info.set_kind(msg.kind());
|
||||
*info.mutable_audio() = *eff;
|
||||
info.set_label(msg.label());
|
||||
|
||||
send_envelope(env);
|
||||
|
||||
auto updated = registry_->set_user_stream(user_id_.load(), info);
|
||||
if (updated) {
|
||||
auto bcast = make_env();
|
||||
auto* ue = bcast.mutable_user_event();
|
||||
ue->set_kind(voicecat::v1::UserEvent::UPDATED);
|
||||
*ue->mutable_user() = *updated;
|
||||
registry_->broadcast(bcast, session_id_);
|
||||
}
|
||||
}
|
||||
|
||||
void ConnSession::handle_stream_stop(const voicecat::v1::StreamStop& msg) {
|
||||
auto updated = registry_->clear_user_stream(user_id_.load(), msg.stream_id());
|
||||
if (msg.stream_id() == announced_stream_id_) announced_stream_id_ = 0;
|
||||
if (updated) {
|
||||
auto bcast = make_env();
|
||||
auto* ue = bcast.mutable_user_event();
|
||||
ue->set_kind(voicecat::v1::UserEvent::UPDATED);
|
||||
*ue->mutable_user() = *updated;
|
||||
registry_->broadcast(bcast, session_id_);
|
||||
}
|
||||
}
|
||||
|
||||
void ConnSession::send_disconnect_and_close(uint32_t code, const std::string& reason) {
|
||||
|
||||
@@ -86,6 +86,7 @@ class ConnSession : public std::enable_shared_from_this<ConnSession> {
|
||||
void handle_ping(const voicecat::v1::Ping& msg);
|
||||
void handle_udp_binding(uint64_t req_id, const voicecat::v1::UdpBinding& msg);
|
||||
void handle_stream_announce(uint64_t req_id, const voicecat::v1::StreamAnnounce& msg);
|
||||
void handle_stream_stop(const voicecat::v1::StreamStop& msg);
|
||||
|
||||
void finish_guest_auth(const voicecat::v1::GuestAuth& guest, uint64_t req_id);
|
||||
void finish_password_auth(const std::string& username, const std::string& password,
|
||||
@@ -119,6 +120,9 @@ class ConnSession : public std::enable_shared_from_this<ConnSession> {
|
||||
mutable std::mutex crypto_mu_;
|
||||
std::unique_ptr<voicecat::crypto::SodiumMediaCrypto> send_crypto_;
|
||||
std::unique_ptr<voicecat::crypto::SodiumMediaCrypto> recv_crypto_;
|
||||
|
||||
// M2: locally-announced stream (single MIC stream per session for now)
|
||||
uint32_t announced_stream_id_{0};
|
||||
};
|
||||
|
||||
} // namespace voicecat::server
|
||||
|
||||
@@ -166,6 +166,37 @@ std::vector<std::shared_ptr<ConnSession>> SessionRegistry::find_channel_sessions
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<voicecat::v1::User> SessionRegistry::set_user_stream(
|
||||
uint32_t user_id, const voicecat::v1::StreamInfo& info) {
|
||||
std::unique_lock lk(mu_);
|
||||
auto it = users_.find(user_id);
|
||||
if (it == users_.end()) return std::nullopt;
|
||||
auto* streams = it->second.proto.mutable_streams();
|
||||
for (int i = 0; i < streams->size(); ++i) {
|
||||
if (streams->Get(i).stream_id() == info.stream_id()) {
|
||||
*streams->Mutable(i) = info;
|
||||
return it->second.proto;
|
||||
}
|
||||
}
|
||||
*streams->Add() = info;
|
||||
return it->second.proto;
|
||||
}
|
||||
|
||||
std::optional<voicecat::v1::User> SessionRegistry::clear_user_stream(uint32_t user_id,
|
||||
uint32_t stream_id) {
|
||||
std::unique_lock lk(mu_);
|
||||
auto it = users_.find(user_id);
|
||||
if (it == users_.end()) return std::nullopt;
|
||||
auto* streams = it->second.proto.mutable_streams();
|
||||
for (int i = 0; i < streams->size(); ++i) {
|
||||
if (streams->Get(i).stream_id() == stream_id) {
|
||||
streams->erase(streams->begin() + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return it->second.proto;
|
||||
}
|
||||
|
||||
uint32_t SessionRegistry::user_channel(uint32_t user_id) const {
|
||||
std::shared_lock lk(mu_);
|
||||
auto it = users_.find(user_id);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <shared_mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
@@ -95,6 +96,15 @@ class SessionRegistry {
|
||||
// Assign an SSRC for a new stream. Returns the assigned SSRC.
|
||||
uint32_t assign_ssrc(uint64_t session_id);
|
||||
|
||||
// Add/replace a stream entry on a user (called when StreamAnnounce succeeds).
|
||||
// Returns the updated User proto for broadcasting, or nullopt if user not found.
|
||||
std::optional<voicecat::v1::User> set_user_stream(uint32_t user_id,
|
||||
const voicecat::v1::StreamInfo& info);
|
||||
|
||||
// Remove a stream entry from a user (called on StreamStop). Returns the updated
|
||||
// User proto for broadcasting, or nullopt if user not found.
|
||||
std::optional<voicecat::v1::User> clear_user_stream(uint32_t user_id, uint32_t stream_id);
|
||||
|
||||
// Get all sessions in a channel except the one excluded (for SFU relay).
|
||||
std::vector<std::shared_ptr<ConnSession>> find_channel_sessions(
|
||||
uint32_t channel_id, uint64_t exclude_session_id = 0) const;
|
||||
|
||||
Reference in New Issue
Block a user