#include "voicecat.h" #include #include #include #include #include #include #include #include #include #include struct ClientState { vc_client* client = nullptr; std::mutex gate; std::condition_variable changed; bool authenticated = false; bool subscribed = false; bool joined = false; uint32_t user = 0; std::vector> streams; std::array received{}; long long energy = 0; uint32_t channels = 0; }; static void event(void* context, const vc_event* value) { auto& state = *static_cast(context); if (value->type == VC_EVENT_SERVER_IDENTITY) { vc_confirm_server_identity(state.client, 1); return; } std::lock_guard lock(state.gate); switch (value->type) { case VC_EVENT_AUTH_RESULT: state.authenticated = value->result == VC_OK; state.user = value->user_id; break; case VC_EVENT_VOICE_STATE: state.subscribed = value->u32a == 1; break; case VC_EVENT_JOIN_RESULT: state.joined = value->result == VC_OK; break; case VC_EVENT_STREAM_STARTED: state.streams.emplace_back(value->user_id, value->stream_id); break; default: break; } state.changed.notify_all(); } static void sink(void* context, uint32_t, uint32_t stream, const int16_t* pcm, size_t samples, uint32_t channels, uint32_t rate) { auto& state = *static_cast(context); if (rate != 48000 || stream >= state.received.size()) return; std::lock_guard lock(state.gate); ++state.received[stream]; state.channels = channels; for (size_t index = 0; index < samples * channels; ++index) state.energy += std::abs(static_cast(pcm[index])); state.changed.notify_all(); } template static bool wait(ClientState& state, Predicate predicate) { std::unique_lock lock(state.gate); return state.changed.wait_for(lock, std::chrono::seconds(8), predicate); } struct Destroy { void operator()(vc_client* client) const { vc_disconnect(client); vc_client_destroy(client); } }; using Client = std::unique_ptr; static Client connect(ClientState& state, uint16_t port, uint32_t channel, const char* nickname) { vc_config config{"dotnet-voice-oracle", "1", VC_LOG_OFF}; Client client(vc_client_create(&config, {event, nullptr, &state})); state.client = client.get(); if (!client || vc_set_external_playback(client.get(), 1) != VC_OK || vc_connect(client.get(), "127.0.0.1", port) != VC_OK || vc_authenticate_guest(client.get(), nickname) != VC_OK || !wait(state, [&] { return state.authenticated; }) || vc_join_channel(client.get(), channel, nullptr) != VC_OK || !wait(state, [&] { return state.joined; }) || vc_join_voice(client.get()) != VC_OK || !wait(state, [&] { return state.subscribed; }) || vc_set_pcm_sink(client.get(), sink, &state) != VC_OK) return {}; return client; } int main(int argc, char** argv) { if (argc != 3) return 1; uint16_t port = static_cast(std::strtoul(argv[1], nullptr, 10)); uint32_t channel = static_cast(std::strtoul(argv[2], nullptr, 10)); ClientState alice, bob; Client a = connect(alice, port, channel, "Native Alice"); Client b = connect(bob, port, channel, "Native Bob"); if (!a || !b) { std::fprintf(stderr, "native authentication/join/subscription failed\n"); return 1; } std::array ids{}; vc_stream_desc mic{}; mic.kind = VC_STREAM_MIC; mic.external_feed = 1; vc_stream_desc screen = mic; screen.kind = VC_STREAM_SCREEN_AUDIO; if (vc_stream_start(a.get(), &mic, &ids[0]) != VC_OK || vc_stream_start(a.get(), &screen, &ids[1]) != VC_OK || vc_stream_start(b.get(), &mic, &ids[2]) != VC_OK || !wait(alice, [&] { return alice.streams.size() >= 3; }) || !wait(bob, [&] { return bob.streams.size() >= 3; })) { std::fprintf(stderr, "native stream signaling failed\n"); return 1; } uint32_t channels = channel == 2 ? 2 : 1; std::vector 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(12000 * std::sin(sample * (side == 0 ? 0.058 : 0.083))); for (int frame = 0; frame < 100; ++frame) { if (vc_stream_feed_pcm(a.get(), ids[0], pcm.data(), 960, channels) != VC_OK || vc_stream_feed_pcm(a.get(), ids[1], pcm.data(), 960, channels) != VC_OK || vc_stream_feed_pcm(b.get(), ids[2], pcm.data(), 960, channels) != VC_OK) return 1; std::this_thread::sleep_for(std::chrono::milliseconds(20)); } bool received = wait(alice, [&] { return alice.received[ids[2]] >= 5 && alice.energy > 0; }) && wait(bob, [&] { return bob.received[ids[0]] >= 5 && bob.received[ids[1]] >= 5 && bob.energy > 0; }); { std::scoped_lock lock(alice.gate, bob.gate); std::printf("channel=%u channels=%u alice=%d bob-mic=%d bob-screen=%d energy=%lld/%lld\n", channel, channels, alice.received[ids[2]], bob.received[ids[0]], bob.received[ids[1]], alice.energy, bob.energy); received = received && alice.channels == channels && bob.channels == channels; } return received ? 0 : 1; }