#include #include #include #include #include "WebClient.h" #include "VoiceBridge.h" using namespace std; using namespace ts; using namespace ts::server; using namespace ts::web; void VoiceBridge::callback_log(void* ptr, pipes::Logger::LogLevel level, const std::string& name, const std::string& message, ...) { auto max_length = 1024 * 8; char buffer[max_length]; va_list args; va_start(args, message); max_length = vsnprintf(buffer, max_length, message.c_str(), args); va_end(args); auto bridge = (VoiceBridge*) ptr; debugMessage(LOG_GENERAL, "{}[WebRTC][{}][{}] {}", CLIENT_STR_LOG_PREFIX_(bridge->owner()), level, name, string(buffer)); } namespace gioloop { void* main_loop_; void*(*g_main_loop_new)(void* /* context */, bool /* is true */); void(*g_main_loop_run)(void* /* loop */); void(*g_main_loop_unref)(void* /* loop */); void*(*g_main_loop_ref)(void* /* loop */); bool initialized{false}; void initialize() { if(initialized) return; initialized = true; g_main_loop_new = (decltype(g_main_loop_new)) dlsym(nullptr, "g_main_loop_new"); g_main_loop_run = (decltype(g_main_loop_run)) dlsym(nullptr, "g_main_loop_run"); g_main_loop_ref = (decltype(g_main_loop_ref)) dlsym(nullptr, "g_main_loop_ref"); g_main_loop_unref = (decltype(g_main_loop_unref)) dlsym(nullptr, "g_main_loop_unref"); if(!g_main_loop_run || !g_main_loop_new || !g_main_loop_ref || !g_main_loop_unref) { logWarning(LOG_INSTANCE, "Missing g_main_loop_new, g_main_loop_run, g_main_loop_ref or g_main_loop_unref functions. Could not spawn main loop."); g_main_loop_run = nullptr; g_main_loop_new = nullptr; return; } main_loop_ = g_main_loop_new(nullptr, false); if(!main_loop_) { logError(LOG_INSTANCE, "Failed to spawn new event loop for the web client."); return; } std::thread([]{ g_main_loop_run(main_loop_); }).detach(); } std::shared_ptr loop() { return std::shared_ptr{(GMainLoop*) g_main_loop_ref(main_loop_), g_main_loop_unref}; } } VoiceBridge::VoiceBridge(const shared_ptr& owner) : _owner(owner) { auto config = make_shared(); config->nice_config = make_shared(); config->nice_config->ice_port_range = {config::web::webrtc_port_min, config::web::webrtc_port_max}; for(const auto& entry : config::web::ice_servers) { auto dp = entry.find(':'); if(dp == string::npos) continue; auto host = entry.substr(0, dp); auto port = entry.substr(dp + 1); if(port.find_last_not_of("0123456789") != string::npos) continue; if(host == "stun.l.google.com" && port == "9302") port = "19302"; /* fix for the invalid config value until 1.3.14beta1 :) */ try { config->nice_config->ice_servers.push_back({host,(uint16_t) stoi(port)}); } catch(std::exception& ex) {} } config->nice_config->ice_servers.push_back({"stun.l.google.com", 19302}); config->nice_config->allow_ice_udp = true; config->nice_config->allow_ice_tcp = false; config->nice_config->use_upnp = config::web::enable_upnp; gioloop::initialize(); config->nice_config->main_loop = gioloop::loop(); /* config->nice_config->main_loop = std::shared_ptr(g_main_loop_new(nullptr, false), g_main_loop_unref); std::thread(g_main_loop_run, config->nice_config->main_loop.get()).detach(); */ config->logger = make_shared(); config->logger->callback_log = VoiceBridge::callback_log; config->logger->callback_argument = this; //config->sctp.local_port = 5202; //Fire Fox don't support a different port :D this->connection = make_unique(config); } VoiceBridge::~VoiceBridge() { __asm__("nop"); } int VoiceBridge::server_id() { auto locked = this->_owner.lock(); return locked ? locked->getServerId() : 0; } std::shared_ptr VoiceBridge::owner() { return this->_owner.lock(); } bool VoiceBridge::initialize(std::string &error) { if(!this->connection->initialize(error)) return false; this->connection->callback_ice_candidate = [&](const rtc::IceCandidate& candidate) { if(!candidate.is_finished_candidate()) { if(auto callback{this->callback_ice_candidate}; callback) callback(candidate); } else { if(auto callback{this->callback_ice_candidate_finished}; callback) callback(); } }; this->connection->callback_new_stream = [&](const std::shared_ptr &channel) { this->handle_media_stream(channel); }; //bind(&VoiceBridge::handle_media_stream, this, placeholders::_1); => crash this->connection->callback_setup_fail = [&](rtc::PeerConnection::ConnectionComponent comp, const std::string& reason) { debugMessage(this->server_id(), "{} WebRTC setup failed! Component {} ({})", CLIENT_STR_LOG_PREFIX_(this->owner()), comp, reason); if(this->callback_failed) this->callback_failed(); }; return true; } bool VoiceBridge::parse_offer(const std::string &sdp) { this->offer_timestamp = chrono::system_clock::now(); string error; return this->connection->apply_offer(error, sdp); } int VoiceBridge::apply_ice(const std::deque>& candidates) { return this->connection->apply_ice_candidates(candidates); } void VoiceBridge::remote_ice_finished() { this->connection->remote_candidates_finished(); } std::string VoiceBridge::generate_answer() { return this->connection->generate_answer(false); } void VoiceBridge::execute_tick() { if(!this->_voice_channel) { if(this->offer_timestamp.time_since_epoch().count() > 0 && this->offer_timestamp + chrono::seconds{20} < chrono::system_clock::now()) { this->offer_timestamp = chrono::system_clock::time_point(); this->connection->callback_setup_fail(rtc::PeerConnection::ConnectionComponent::BASE, "setup timeout"); } } } void VoiceBridge::handle_media_stream(const std::shared_ptr &undefined_stream) { if(undefined_stream->type() == rtc::CHANTYPE_APPLICATION) { auto stream = dynamic_pointer_cast(undefined_stream); if(!stream) return; stream->callback_datachannel_new = [&](const std::shared_ptr &channel) { this->handle_data_channel(channel); }; //bind(&VoiceBridge::handle_data_channel, this, placeholders::_1); => may crash? } else if(undefined_stream->type() == rtc::CHANTYPE_AUDIO) { auto stream = dynamic_pointer_cast(undefined_stream); if(!stream) return; this->_audio_channel = stream; for(const auto& ex : stream->list_extensions()) { debugMessage(0, "{} | {}", ex->name, ex->id); } stream->register_local_extension("urn:ietf:params:rtp-hdrext:ssrc-audio-level"); for(const auto& codec : stream->list_codecs()) { if(codec->type == rtc::codec::Codec::OPUS) { codec->accepted = true; break; } } stream->incoming_data_handler = [&](const std::shared_ptr &channel, const pipes::buffer_view &data, size_t payload_offset) { this->handle_audio_data(channel, data, payload_offset); }; } else { logError(this->server_id(), "Got offer for unknown channel of type {}", undefined_stream->type()); } } void VoiceBridge::handle_data_channel(const std::shared_ptr &channel) { if(channel->lable() == "main") { this->_voice_channel = channel; debugMessage(this->server_id(), "{} Got voice channel!", CLIENT_STR_LOG_PREFIX_(this->owner())); this->callback_initialized(); } weak_ptr weak_channel = channel; channel->callback_binary = [&, weak_channel](const pipes::buffer_view& buffer) { this->callback_voice_data(buffer.view(2), buffer[0] == 1, buffer[1] == 1); /* buffer.substr(2), buffer[0] == 1, buffer[1] == 1 */ }; channel->callback_close = [&, channel] { if(channel == this->_voice_channel) { this->_voice_channel = nullptr; //TODO may callback? debugMessage(this->server_id(), "{} Voice channel disconnected!", CLIENT_STR_LOG_PREFIX_(this->owner())); } }; } void VoiceBridge::handle_audio_data(const std::shared_ptr &channel, const pipes::buffer_view &data, size_t payload_offset) { if(channel->codec->type != rtc::codec::Codec::OPUS) { debugMessage(this->server_id(), "{} Got unknown codec ({})!", CLIENT_STR_LOG_PREFIX_(this->owner()), channel->codec->type); return; } auto ac = _audio_channel.lock(); if(!ac) return; for(const auto& ext : ac->list_extensions(rtc::direction::incoming)) { if(ext->name == "urn:ietf:params:rtp-hdrext:ssrc-audio-level") { int level; if(rtc::protocol::rtp_header_extension_parse_audio_level(data, ext->id, &level) == 0) { //debugMessage(this->server_id(), "Audio level: {}", level); if(level == 127) return; //Silence } break; } } //int level; //rtc::protocol::rtp_header_extension_parse_audio_level((char*) data.data(), data.length(), 1, &level); auto target_buffer = buffer::allocate_buffer(data.length() - payload_offset + 3); le2be16(this->voice.packet_id++, (char*) target_buffer.data_ptr()); target_buffer[2] = 5; memcpy(&target_buffer[3], &data[payload_offset], data.length() - payload_offset); this->callback_voice_data(target_buffer, this->voice.packet_id < 7, false); }