128 lines
5.4 KiB
C++
128 lines
5.4 KiB
C++
#include "voicecat.h"
|
|||
|
|
|
||
|
|
#include <array>
|
||
|
|
#include <chrono>
|
||
|
|
#include <cmath>
|
||
|
|
#include <condition_variable>
|
||
|
|
#include <cstdio>
|
||
|
|
#include <cstdlib>
|
||
|
|
#include <memory>
|
||
|
|
#include <mutex>
|
||
|
|
#include <thread>
|
||
|
|
#include <vector>
|
||
|
|
|
||
|
|
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<std::pair<uint32_t, uint32_t>> streams;
|
||
|
|
std::array<int, 3> received{};
|
||
|
|
long long energy = 0;
|
||
|
|
uint32_t channels = 0;
|
||
|
|
};
|
||
|
|
|
||
|
|
static void event(void* context, const vc_event* value) {
|
||
|
|
auto& state = *static_cast<ClientState*>(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<ClientState*>(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<int>(pcm[index]));
|
||
|
|
state.changed.notify_all();
|
||
|
|
}
|
||
|
|
|
||
|
|
template<class Predicate>
|
||
|
|
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<vc_client, Destroy>;
|
||
|
|
|
||
|
|
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<uint16_t>(std::strtoul(argv[1], nullptr, 10));
|
||
|
|
uint32_t channel = static_cast<uint32_t>(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<uint32_t, 3> 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<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 ? 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;
|
||
|
|
}
|