TeaSpeak-Client/native/serverconnection/src/connection/audio/VoiceConnection.cpp

397 lines
12 KiB
C++

#include "VoiceConnection.h"
#include "VoiceClient.h"
#include "../ServerConnection.h"
#include "AudioSender.h"
#include "../../audio/js/AudioConsumer.h"
#include "../../audio/AudioInput.h"
#include "../../logger.h"
#include <misc/endianness.h> /* MUST be included as last file */
using namespace std;
using namespace tc;
using namespace tc::connection;
using namespace ts;
using namespace ts::protocol;
using namespace audio::recorder;
VoiceConnectionWrap::VoiceConnectionWrap(const std::shared_ptr<VoiceConnection>& handle) : handle(handle) {}
VoiceConnectionWrap::~VoiceConnectionWrap() {
if(!this->_voice_recoder_handle.IsEmpty()) {
auto old_consumer = this->_voice_recoder_ptr;
assert(old_consumer);
std::lock_guard read_lock{old_consumer->native_read_callback_lock};
old_consumer->native_read_callback = nullptr;
}
}
void VoiceConnectionWrap::do_wrap(const v8::Local<v8::Object> &object) {
this->Wrap(object);
}
NAN_MODULE_INIT(VoiceConnectionWrap::Init) {
auto klass = Nan::New<v8::FunctionTemplate>(VoiceConnectionWrap::NewInstance);
klass->SetClassName(Nan::New("VoiceConnection").ToLocalChecked());
klass->InstanceTemplate()->SetInternalFieldCount(1);
Nan::SetPrototypeMethod(klass, "decoding_supported", VoiceConnectionWrap::_decoding_supported);
Nan::SetPrototypeMethod(klass, "encoding_supported", VoiceConnectionWrap::_encoding_supported);
Nan::SetPrototypeMethod(klass, "register_client", VoiceConnectionWrap::register_client);
Nan::SetPrototypeMethod(klass, "available_clients", VoiceConnectionWrap::available_clients);
Nan::SetPrototypeMethod(klass, "unregister_client", VoiceConnectionWrap::unregister_client);
Nan::SetPrototypeMethod(klass, "audio_source", VoiceConnectionWrap::audio_source);
Nan::SetPrototypeMethod(klass, "set_audio_source", VoiceConnectionWrap::set_audio_source);
Nan::SetPrototypeMethod(klass, "get_encoder_codec", VoiceConnectionWrap::get_encoder_codec);
Nan::SetPrototypeMethod(klass, "set_encoder_codec", VoiceConnectionWrap::set_encoder_codec);
Nan::SetPrototypeMethod(klass, "enable_voice_send", VoiceConnectionWrap::enable_voice_send);
constructor().Reset(Nan::GetFunction(klass).ToLocalChecked());
}
NAN_METHOD(VoiceConnectionWrap::NewInstance) {
if(!info.IsConstructCall())
Nan::ThrowError("invalid invoke!");
}
NAN_METHOD(VoiceConnectionWrap::_connected) {
info.GetReturnValue().Set(true);
}
NAN_METHOD(VoiceConnectionWrap::_encoding_supported) {
if(info.Length() != 1) {
Nan::ThrowError("invalid argument count");
return;
}
auto codec = info[0]->Uint32Value(Nan::GetCurrentContext()).FromMaybe(0);
info.GetReturnValue().Set(codec >= 4 && codec <= 5); /* ignore SPEX currently :/ */
}
NAN_METHOD(VoiceConnectionWrap::_decoding_supported) {
if(info.Length() != 1) {
Nan::ThrowError("invalid argument count");
return;
}
auto codec = info[0]->Uint32Value(Nan::GetCurrentContext()).FromMaybe(0);
info.GetReturnValue().Set(codec >= 4 && codec <= 5); /* ignore SPEX currently :/ */
}
NAN_METHOD(VoiceConnectionWrap::register_client) {
auto connection = ObjectWrap::Unwrap<VoiceConnectionWrap>(info.Holder());
if(info.Length() != 1) {
Nan::ThrowError("invalid argument count");
return;
}
auto id = info[0]->Uint32Value(Nan::GetCurrentContext()).FromMaybe(0);
auto handle = connection->handle.lock();
if(!handle) {
Nan::ThrowError("handle has been deallocated");
return;
}
auto client = handle->register_client(id);
if(!client) {
Nan::ThrowError("failed to register client");
return;
}
client->initialize_js_object();
info.GetReturnValue().Set(client->js_handle());
}
NAN_METHOD(VoiceConnectionWrap::available_clients) {
auto connection = ObjectWrap::Unwrap<VoiceConnectionWrap>(info.Holder());
auto handle = connection->handle.lock();
if(!handle) {
Nan::ThrowError("handle has been deallocated");
return;
}
auto client = handle->clients();
v8::Local<v8::Array> result = Nan::New<v8::Array>((int) client.size());
for(uint32_t index{0}; index < client.size(); index++)
Nan::Set(result, index, client[index]->js_handle());
info.GetReturnValue().Set(result);
}
NAN_METHOD(VoiceConnectionWrap::unregister_client) {
auto connection = ObjectWrap::Unwrap<VoiceConnectionWrap>(info.Holder());
if(info.Length() != 1) {
Nan::ThrowError("invalid argument count");
return;
}
auto id = info[0]->Uint32Value(Nan::GetCurrentContext()).FromMaybe(0);
auto handle = connection->handle.lock();
if(!handle) {
Nan::ThrowError("handle has been deallocated");
return;
}
auto client = handle->find_client(id);
if(!client) {
Nan::ThrowError("missing client");
return;
}
client->finalize_js_object();
handle->delete_client(client);
}
NAN_METHOD(VoiceConnectionWrap::audio_source) {
auto client = ObjectWrap::Unwrap<VoiceConnectionWrap>(info.Holder());
info.GetReturnValue().Set(client->_voice_recoder_handle.Get(info.GetIsolate()));
}
NAN_METHOD(VoiceConnectionWrap::set_audio_source) {
auto connection = ObjectWrap::Unwrap<VoiceConnectionWrap>(info.Holder());
if(info.Length() != 1) {
Nan::ThrowError("invalid argument count");
return;
}
if(!Nan::New(AudioConsumerWrapper::constructor_template())->HasInstance(info[0]) && !info[0]->IsNullOrUndefined()) {
Nan::ThrowError("invalid consumer (Consumer must be native!)");
return;
}
auto handle = connection->handle.lock();
if(!handle) {
Nan::ThrowError("handle has been deallocated");
return;
}
connection->release_recorder();
if(!info[0]->IsNullOrUndefined()) {
connection->_voice_recoder_ptr = ObjectWrap::Unwrap<audio::recorder::AudioConsumerWrapper>(info[0]->ToObject(Nan::GetCurrentContext()).ToLocalChecked());
connection->_voice_recoder_handle.Reset(info[0]->ToObject(Nan::GetCurrentContext()).ToLocalChecked());
weak_ptr weak_handle = handle;
auto sample_rate = connection->_voice_recoder_ptr->sample_rate();
auto channels = connection->_voice_recoder_ptr->channel_count();
lock_guard read_lock{connection->_voice_recoder_ptr->native_read_callback_lock};
connection->_voice_recoder_ptr->native_read_callback = [weak_handle, sample_rate, channels](const float* buffer, size_t sample_count) {
auto handle = weak_handle.lock();
if(!handle) {
log_warn(category::audio, tr("Missing voice connection handle. Dropping input!"));
return;
}
auto sender = handle->voice_sender();
if(sender) {
if(sample_count > 0 && buffer) {
sender->send_data(buffer, sample_count, sample_rate, channels);
} else {
sender->send_stop();
}
} else {
log_warn(category::audio, tr("Missing voice connection audio sender. Dropping input!"));
return;
}
};
}
}
NAN_METHOD(VoiceConnectionWrap::get_encoder_codec) {
auto connection = ObjectWrap::Unwrap<VoiceConnectionWrap>(info.Holder());
auto handle = connection->handle.lock();
if(!handle) {
Nan::ThrowError("handle has been deallocated");
return;
}
auto codec = handle->get_encoder_codec();
info.GetReturnValue().Set(audio::codec::audio_codec_to_protocol_id(codec).value_or(-1));
}
NAN_METHOD(VoiceConnectionWrap::set_encoder_codec) {
auto connection = ObjectWrap::Unwrap<VoiceConnectionWrap>(info.Holder());
auto handle = connection->handle.lock();
if(!handle) {
Nan::ThrowError("handle has been deallocated");
return;
}
if(info.Length() != 1 || !info[0]->IsNumber()) {
Nan::ThrowError("Invalid arguments");
return;
}
auto codec = audio::codec::audio_codec_from_protocol_id(info[0]->NumberValue(Nan::GetCurrentContext()).FromMaybe(-1));
if(!codec.has_value()) {
Nan::ThrowError("unknown codec id");
return;
}
handle->set_encoder_codec(*codec);
}
NAN_METHOD(VoiceConnectionWrap::enable_voice_send) {
auto _this = ObjectWrap::Unwrap<VoiceConnectionWrap>(info.Holder());
auto handle = _this->handle.lock();
if(!handle) {
Nan::ThrowError("handle has been deallocated");
return;
}
if(info.Length() != 1 || !info[0]->IsBoolean()) {
Nan::ThrowError("Invalid arguments");
return;
}
auto sender = handle->voice_sender();
if(!sender) {
Nan::ThrowError("Voice sender has been deallocated");
return;
}
sender->set_voice_send_enabled(info[0]->BooleanValue(info.GetIsolate()));
}
void VoiceConnectionWrap::release_recorder() {
if(!this->_voice_recoder_handle.IsEmpty()) {
assert(v8::Isolate::GetCurrent());
auto old_consumer = this->_voice_recoder_ptr;
assert(old_consumer);
lock_guard read_lock(this->_voice_recoder_ptr->native_read_callback_lock);
this->_voice_recoder_ptr->native_read_callback = nullptr;
} else {
assert(!this->_voice_recoder_ptr);
}
this->_voice_recoder_ptr = nullptr;
this->_voice_recoder_handle.Reset();
}
VoiceConnection::VoiceConnection(ServerConnection *handle) : _handle(handle) {
this->_voice_sender = make_shared<VoiceSender>(this);
this->_voice_sender->_ref = this->_voice_sender;
this->_voice_sender->set_codec(audio::codec::AudioCodec::OpusMusic);
}
VoiceConnection::~VoiceConnection() {
if(v8::Isolate::GetCurrent())
this->finalize_js_object();
else {
assert(this->_js_handle.IsEmpty());
}
this->_voice_sender->finalize();
}
void VoiceConnection::reset() {
lock_guard lock{this->_clients_lock};
this->_clients.clear();
}
void VoiceConnection::execute_tick() {
decltype(this->_clients) clients{};
{
std::lock_guard lg{this->_clients_lock};
clients = this->_clients;
}
for(auto& client : clients)
client->execute_tick();
}
void VoiceConnection::initialize_js_object() {
auto object_wrap = new VoiceConnectionWrap(this->ref());
auto object = Nan::NewInstance(Nan::New(VoiceConnectionWrap::constructor()), 0, nullptr).ToLocalChecked();
object_wrap->do_wrap(object);
this->_js_handle.Reset(Nan::GetCurrentContext()->GetIsolate(), object);
}
void VoiceConnection::finalize_js_object() {
this->_js_handle.Reset();
}
std::shared_ptr<VoiceClient> VoiceConnection::find_client(uint16_t client_id) {
lock_guard lock{this->_clients_lock};
for(const auto& client : this->_clients)
if(client->client_id() == client_id)
return client;
return nullptr;
}
std::shared_ptr<VoiceClient> VoiceConnection::register_client(uint16_t client_id) {
lock_guard lock{this->_clients_lock};
auto client = this->find_client(client_id);
if(client) return client;
client = make_shared<VoiceClient>(this->ref(), client_id);
client->ref_ = client;
client->initialize();
this->_clients.push_back(client);
return client;
}
void VoiceConnection::delete_client(const std::shared_ptr<tc::connection::VoiceClient> &client) {
{
lock_guard lock(this->_clients_lock);
auto it = find(this->_clients.begin(), this->_clients.end(), client);
if(it != this->_clients.end()) {
this->_clients.erase(it);
}
}
//TODO deinitialize client
}
void VoiceConnection::process_packet(const ts::protocol::PacketParser &packet) {
if(packet.type() == ts::protocol::PacketType::VOICE) {
if(packet.payload_length() < 5) {
//TODO log invalid voice packet
return;
}
auto payload = packet.payload();
auto packet_id = be2le16(&payload[0]);
auto client_id = be2le16(&payload[2]);
auto codec_id = (uint8_t) payload[4];
auto flag_head = packet.has_flag(ts::protocol::PacketFlag::Compressed);
//container->voice_data = packet->data().length() > 5 ? packet->data().range(5) : pipes::buffer{};
//log_info(category::voice_connection, tr("Received voice packet from {}. Packet ID: {}"), client_id, packet_id);
auto client = this->find_client(client_id);
if(!client) {
log_warn(category::voice_connection, tr("Received voice packet from unknown client {}. Dropping packet!"), client_id);
return;
}
if(payload.length() > 5) {
client->process_packet(packet_id, payload.view(5), codec_id, flag_head);
} else {
client->process_packet(packet_id, pipes::buffer_view{nullptr, 0}, codec_id, flag_head);
}
} else {
//TODO implement whisper
}
}
void VoiceConnection::set_encoder_codec(const audio::codec::AudioCodec &codec) {
auto vs = this->_voice_sender;
if(vs) {
vs->set_codec(codec);
}
}
audio::codec::AudioCodec VoiceConnection::get_encoder_codec() {
auto vs = this->_voice_sender;
return vs ? vs->target_codec() : audio::codec::AudioCodec::Unknown;
}