Files
voice-cat/dotnet/oracle/voice.cpp
T

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;
}