diff --git a/CMakeLists.txt b/CMakeLists.txt index 90012c8f..0ad30109 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -180,14 +180,15 @@ function(sono_add_custom_plugin_target target_name product_name formats is_instr set (HEADER_INCLUDES - deps/aoo/lib + deps/aoo + deps/aoo/include deps/aoo/deps ) set (LIB_PATHS "") set (PLAT_COMPILE_DEFS - $<$:LOGLEVEL=2> + $<$:AOO_LOGLEVEL=AOO_LOGLEVEL_VERBOSE> USE_CODEC_OPUS=1 AOO_TIMEFILTER_CHECK=0 AOO_STATIC) @@ -302,49 +303,60 @@ function(sono_add_custom_plugin_target target_name product_name formats is_instr Source/mtdm.h Source/zitaRev.h ) - set(AOOSourceFiles - deps/aoo/lib/src/SLIP.hpp - deps/aoo/lib/src/client.cpp - deps/aoo/lib/src/client.hpp - deps/aoo/lib/src/codec_opus.cpp - deps/aoo/lib/src/codec_pcm.cpp - deps/aoo/lib/src/common.cpp - deps/aoo/lib/src/common.hpp - deps/aoo/lib/src/lockfree.hpp - deps/aoo/lib/src/net_utils.cpp - deps/aoo/lib/src/net_utils.hpp - deps/aoo/lib/src/server.cpp - deps/aoo/lib/src/server.hpp - deps/aoo/lib/src/sink.cpp - deps/aoo/lib/src/sink.hpp - deps/aoo/lib/src/source.cpp - deps/aoo/lib/src/source.hpp - deps/aoo/lib/src/sync.cpp - deps/aoo/lib/src/sync.hpp - deps/aoo/lib/src/time.cpp - deps/aoo/lib/src/time.hpp - deps/aoo/lib/src/time_dll.hpp - deps/aoo/lib/aoo/aoo.h - deps/aoo/lib/aoo/aoo.hpp - deps/aoo/lib/aoo/aoo_net.h - deps/aoo/lib/aoo/aoo_net.hpp - deps/aoo/lib/aoo/aoo_opus.h - deps/aoo/lib/aoo/aoo_pcm.h - deps/aoo/lib/aoo/aoo_types.h - deps/aoo/lib/aoo/aoo_utils.hpp - - deps/aoo/deps/md5/md5.c + set(AOOSourceFiles + deps/aoo/aoo/src/source.cpp + deps/aoo/aoo/src/codec.hpp + deps/aoo/aoo/src/net + deps/aoo/aoo/src/net/SLIP.hpp + deps/aoo/aoo/src/net/client.cpp + deps/aoo/aoo/src/net/server.hpp + deps/aoo/aoo/src/net/commands.hpp + deps/aoo/aoo/src/net/client.hpp + deps/aoo/aoo/src/net/server.cpp + deps/aoo/aoo/src/sink.cpp + deps/aoo/aoo/src/resampler.hpp + deps/aoo/aoo/src/codec + deps/aoo/aoo/src/codec/opus.cpp + deps/aoo/aoo/src/codec/pcm.cpp + deps/aoo/aoo/src/timer.cpp + deps/aoo/aoo/src/imp.hpp + deps/aoo/aoo/src/buffer.cpp + deps/aoo/aoo/src/aoo.cpp + deps/aoo/aoo/src/timer.hpp + deps/aoo/aoo/src/time_dll.hpp + deps/aoo/aoo/src/buffer.hpp + deps/aoo/aoo/src/source.hpp + deps/aoo/aoo/src/sink.hpp + deps/aoo/aoo/src/resampler.cpp + deps/aoo/common/lockfree.hpp + deps/aoo/common/net_utils.hpp + deps/aoo/common/time.hpp + deps/aoo/common/sync.cpp + deps/aoo/common/sync.hpp + deps/aoo/common/ntp.cpp + deps/aoo/common/utils.hpp + deps/aoo/common/net_utils.cpp + deps/aoo/common/time.cpp + deps/aoo/include/aoo/aoo_types.h + deps/aoo/include/aoo/aoo_net.h + deps/aoo/include/aoo/codec + deps/aoo/include/aoo/codec/aoo_opus.h + deps/aoo/include/aoo/codec/aoo_pcm.h + deps/aoo/include/aoo/aoo_net.hpp + deps/aoo/include/aoo/aoo.hpp + deps/aoo/include/aoo/aoo.h deps/aoo/deps/md5/md5.h + deps/aoo/deps/md5/md5.c deps/aoo/deps/oscpack/osc/OscOutboundPacketStream.cpp - deps/aoo/deps/oscpack/osc/OscPrintReceivedElements.cpp - deps/aoo/deps/oscpack/osc/OscReceivedElements.cpp + deps/aoo/deps/oscpack/osc/OscPacketListener.h deps/aoo/deps/oscpack/osc/OscTypes.cpp + deps/aoo/deps/oscpack/osc/OscPrintReceivedElements.h deps/aoo/deps/oscpack/osc/MessageMappingOscPacketListener.h - deps/aoo/deps/oscpack/osc/OscException.h deps/aoo/deps/oscpack/osc/OscHostEndianness.h + deps/aoo/deps/oscpack/osc/OscException.h deps/aoo/deps/oscpack/osc/OscOutboundPacketStream.h - deps/aoo/deps/oscpack/osc/OscPacketListener.h - deps/aoo/deps/oscpack/osc/OscPrintReceivedElements.h + deps/aoo/deps/oscpack/osc/OscReceivedElements.cpp + deps/aoo/deps/oscpack/osc/OscPrintReceivedElements.cpp deps/aoo/deps/oscpack/osc/OscReceivedElements.h deps/aoo/deps/oscpack/osc/OscTypes.h ) diff --git a/Source/SonobusPluginProcessor.cpp b/Source/SonobusPluginProcessor.cpp index 243cc4e9..564b6cb4 100644 --- a/Source/SonobusPluginProcessor.cpp +++ b/Source/SonobusPluginProcessor.cpp @@ -10,8 +10,12 @@ #include "aoo/aoo_net.h" -#include "aoo/aoo_pcm.h" -#include "aoo/aoo_opus.h" + +#include "aoo/codec/aoo_pcm.h" +#include "aoo/codec/aoo_opus.h" + +#include "aoo/aoo_net.hpp" +#include "common/net_utils.hpp" #include "oscpack/osc/OscOutboundPacketStream.h" #include "oscpack/osc/OscReceivedElements.h" @@ -41,6 +45,8 @@ typedef int socklen_t; #define SENDBUFSIZE_SCALAR 2.0f #define PEER_PING_INTERVAL_MS 2000.0 +#define LOCAL_SERVER_PORT 10999 + String SonobusAudioProcessor::paramInGain ("ingain"); String SonobusAudioProcessor::paramDry ("dry"); String SonobusAudioProcessor::paramInMonitorMonoPan ("inmonmonopan"); @@ -177,6 +183,7 @@ static addrinfo* getAddressInfo (bool isDatagram, const String& hostName, int po return nullptr; } +#if 0 struct SonobusAudioProcessor::EndpointState { EndpointState(String ipaddr_="", int port_=0) : ipaddr(ipaddr_), port(port_) { rawaddr.sa_family = AF_UNSPEC; @@ -212,7 +219,33 @@ struct SonobusAudioProcessor::EndpointState { struct sockaddr rawaddr; }; +#else +struct SonobusAudioProcessor::EndpointState { + EndpointState(aoo::ip_address addr) : address(addr) { + + ipaddr = address.name_unmapped(); + port = address.port(); + } + + int ownerSocket = -1; + + String ipaddr; + int port = 0; + + + struct sockaddr * getRawAddr() { + return address.address_ptr(); + } + + + // runtime state + int64_t sentBytes = 0; + int64_t recvBytes = 0; + + aoo::ip_address address; +}; +#endif @@ -226,9 +259,16 @@ enum { RemoteNetTypeMobileData = 3 }; +struct ProcessorIdPair +{ + ProcessorIdPair(SonobusAudioProcessor *proc=nullptr, int32_t id_=-1) : processor(proc), id(id_) {} + SonobusAudioProcessor * processor; + int32_t id; +}; + struct SonobusAudioProcessor::RemotePeer { - RemotePeer(EndpointState * ep = 0, int id_=0, aoo::isink::pointer oursink_ = 0, aoo::isource::pointer oursource_ = 0) : endpoint(ep), + RemotePeer(EndpointState * ep = 0, int id_=0, aoo::sink::pointer oursink_ = 0, aoo::source::pointer oursource_ = 0) : endpoint(ep), ourId(id_), oursink(std::move(oursink_)), oursource(std::move(oursource_)) { for (int i=0; i < MAX_PANNERS; ++i) { @@ -240,27 +280,36 @@ struct SonobusAudioProcessor::RemotePeer { } } - oursink.reset(aoo::isink::create(ourId)); - oursource.reset(aoo::isource::create(ourId)); + oursink.reset(aoo::sink::create(ourId, 0)); + oursource.reset(aoo::source::create(ourId, 0)); // create latency sink/sources - latencysink.reset(aoo::isink::create(ourId + LATENCY_ID_OFFSET)); - latencysource.reset(aoo::isource::create(ourId + LATENCY_ID_OFFSET)); - echosink.reset(aoo::isink::create(ourId + ECHO_ID_OFFSET)); - echosource.reset(aoo::isource::create(ourId + ECHO_ID_OFFSET)); + latencysink.reset(aoo::sink::create(ourId + LATENCY_ID_OFFSET, 0)); + latencysource.reset(aoo::source::create(ourId + LATENCY_ID_OFFSET, 0)); + echosink.reset(aoo::sink::create(ourId + ECHO_ID_OFFSET, 0)); + echosource.reset(aoo::source::create(ourId + ECHO_ID_OFFSET, 0)); } EndpointState * endpoint = 0; int32_t ourId = AOO_ID_NONE; int32_t remoteSinkId = AOO_ID_NONE; int32_t remoteSourceId = AOO_ID_NONE; - aoo::isink::pointer oursink; - aoo::isource::pointer oursource; + aoo::sink::pointer oursink; + aoo::source::pointer oursource; + + ProcessorIdPair oursinkpp; + ProcessorIdPair oursourcepp; + + + aoo::sink::pointer latencysink; + aoo::source::pointer latencysource; + aoo::sink::pointer echosink; + aoo::source::pointer echosource; + ProcessorIdPair latsinkpp; + ProcessorIdPair latsourcepp; + ProcessorIdPair echosinkpp; + ProcessorIdPair echosourcepp; - aoo::isink::pointer latencysink; - aoo::isource::pointer latencysource; - aoo::isink::pointer echosink; - aoo::isource::pointer echosource; bool activeLatencyTest = false; std::unique_ptr latencyProcessor; std::unique_ptr latencyMeasurer; @@ -345,8 +394,11 @@ struct SonobusAudioProcessor::RemotePeer { bool remoteIsRecording = false; bool hasRemoteInfo = false; + aoo_net_peer_info aooPeerInfo; + std::unique_ptr fileWriter; + ReadWriteLock sinkLock; }; @@ -356,12 +408,14 @@ static int32_t endpoint_send(void *e, const char *data, int32_t size) { SonobusAudioProcessor::EndpointState * endpoint = static_cast(e); int result = -1; - if (endpoint->peer) { - result = endpoint->owner->write(*(endpoint->peer), data, size); - } else { - result = endpoint->owner->write(endpoint->ipaddr, endpoint->port, data, size); - } - + //if (endpoint->peer) { + // result = endpoint->owner->write(*(endpoint->peer), data, size); + //} else { + // result = endpoint->owner->write(endpoint->ipaddr, endpoint->port, data, size); + //} + + result = aoo::socket_sendto(endpoint->ownerSocket, data, size, endpoint->address); + if (result > 0) { // include UDP overhead endpoint->sentBytes += result + UDP_OVERHEAD_BYTES; @@ -372,26 +426,22 @@ static int32_t endpoint_send(void *e, const char *data, int32_t size) return result; } -static int32_t client_send(void *e, const char *data, int32_t size, void *raddr) +int32_t SonobusAudioProcessor::udpsend(void *user, const char *msg, int32_t size, + const void *addr, int32_t addrlen, uint32_t flags) { - SonobusAudioProcessor::EndpointState * endpoint = static_cast(e); - int result = -1; + auto x = (SonobusAudioProcessor *)user; + aoo::ip_address address((const sockaddr *)addr, addrlen); - const struct sockaddr *addr = (struct sockaddr *)raddr; - - if (addr->sa_family == AF_INET){ - result = (int) ::sendto(endpoint->owner->getRawSocketHandle(), data, (size_t)size, 0, addr, sizeof(struct sockaddr_in)); + // lookup endpoint + if (auto * es = x->findEndpoint(address)) { + return endpoint_send(es, msg, size); } - - if (result > 0) { - endpoint->sentBytes += result + UDP_OVERHEAD_BYTES; + else { + return aoo::socket_sendto(x->mUdpSocketHandle, msg, size, address); } - - return result; } - class SonobusAudioProcessor::SendThread : public juce::Thread { public: @@ -433,10 +483,8 @@ class SonobusAudioProcessor::RecvThread : public juce::Thread void run() override { while (!threadShouldExit()) { - - if (_processor.mUdpSocket->waitUntilReady(true, 20) == 1) { - _processor.doReceiveData(); - } + + _processor.doReceiveData(); } DBG("Recv thread finishing"); @@ -816,8 +864,7 @@ void SonobusAudioProcessor::initializeAoo(int udpPort) //mAooSink.reset(aoo::isink::create(1)); - mAooDummySource.reset(aoo::isource::create(0)); - + mAooDummySource.reset(aoo::source::create(0, 0)); @@ -829,9 +876,13 @@ void SonobusAudioProcessor::initializeAoo(int udpPort) - mUdpSocket = std::make_unique(); - mUdpSocket->setSendBufferSize(1048576); - mUdpSocket->setReceiveBufferSize(1048576); + //mUdpSocket = std::make_unique(); + //mUdpSocket->setSendBufferSize(1048576); + //mUdpSocket->setReceiveBufferSize(1048576); + + + + /* int tos_local = 0x38; // QOS realtime DSCP @@ -844,9 +895,14 @@ void SonobusAudioProcessor::initializeAoo(int udpPort) if (udpport > 0) { int attempts = 100; while (attempts > 0) { - if (mUdpSocket->bindToPort(udpport)) { - udpport = mUdpSocket->getBoundPort(); + + int sock = aoo::socket_udp(udpport); + + //if (mUdpSocket->bindToPort(udpport)) { + // udpport = mUdpSocket->getBoundPort(); + if (sock >= 0) { DBG("Bound udp port to " << udpport); + mUdpSocketHandle = sock; break; } ++udpport; @@ -860,18 +916,44 @@ void SonobusAudioProcessor::initializeAoo(int udpPort) } else { // system assigned - if (!mUdpSocket->bindToPort(0)) { + int sock = aoo::socket_udp(0); + + //if (!mUdpSocket->bindToPort(0)) { + if (sock < 0) { DBG("Error binding to any udp port!"); } else { - udpport = mUdpSocket->getBoundPort(); - DBG("Bound system chosen udp port to " << udpport); + //udpport = mUdpSocket->getBoundPort(); + mUdpSocketHandle = sock; } } - + if (mUdpSocketHandle >= 0) { + // increase socket buffers + const int sendbufsize = 1 << 19; // 512 Kb + const int recvbufsize = 1 << 20; // 1 MB + aoo::socket_setsendbufsize(mUdpSocketHandle, sendbufsize); + aoo::socket_setrecvbufsize(mUdpSocketHandle, recvbufsize); + + aoo::ip_address addr; + if (aoo::socket_address(mUdpSocketHandle, addr) != 0){ + DBG("AooNode: couldn't get socket address"); + aoo::socket_close(mUdpSocketHandle); + mUdpSocketHandle = -1; + udpport = 0; + } + else { + udpport = addr.port(); + mLocalClientAddress = addr; + } + + } + mUdpLocalPort = udpport; + DBG("Bound udp port to " << mUdpLocalPort); + + //mLocalIPAddress = IPAddress::getLocalAddress(); #if JUCE_IOS @@ -903,11 +985,22 @@ void SonobusAudioProcessor::initializeAoo(int udpPort) } #endif - mServerEndpoint = std::make_unique(); - mServerEndpoint->owner = mUdpSocket.get(); + //mServerEndpoint = std::make_unique(); + //mServerEndpoint->owner = mUdpSocket.get(); - if (mUdpLocalPort > 0) { - mAooClient.reset(aoo::net::iclient::create(mServerEndpoint.get(), client_send, mUdpLocalPort)); + if (mUdpSocketHandle >= 0) { + aoo::ip_address udpaddr; + if (socket_address(mUdpSocketHandle, udpaddr) != 0){ + DBG("Could not get udp socket address"); + } else { + mAooClient.reset(aoo::net::client::create(udpaddr.address(), udpaddr.length(), 0)); + + + mAooClient->set_eventhandler( + [](void *user, const aoo_event *event, int32_t level) { + static_cast(user)->handleAooClientEvent(event, level); + }, this, AOO_EVENT_CALLBACK); + } } @@ -915,14 +1008,26 @@ void SonobusAudioProcessor::initializeAoo(int udpPort) mSendThread->setPriority(9); mRecvThread = std::make_unique(*this); mRecvThread->setPriority(9); + + mEventThread = std::make_unique(*this); if (mAooClient) { mClientThread = std::make_unique(*this); + + mAooClient->add_source(mAooDummySource.get(), 0); + + mAooDummySource->set_eventhandler( + [](void *user, const aoo_event *event, int32_t level){ + auto * pp = static_cast(user); + pp->handleAooSourceEvent(event, level, 0); + }, this, AOO_EVENT_CALLBACK); + } mSendThread->startThread(); mRecvThread->startThread(); + mEventThread->startThread(); if (mAooClient) { @@ -943,7 +1048,20 @@ void SonobusAudioProcessor::cleanupAoo() mEventThread->stopThread(400); if (mAooClient) { - mAooClient->disconnect(); + auto cb = [](void *y, aoo_error result, const void *data){ + auto x = (SonobusAudioProcessor *)y; + if (result == AOO_OK){ + DBG("Disconnected"); + } else { + auto reply = (const aoo_net_error_reply *)data; + if (reply) { + DBG("Error disconnecting: " << reply->error_code << " msg: " << reply->error_message); + } + } + }; + + mAooClient->disconnect(cb, this); + mAooClient->quit(); mClientThread->stopThread(400); } @@ -954,7 +1072,10 @@ void SonobusAudioProcessor::cleanupAoo() mAooClient.reset(); - mUdpSocket.reset(); + if (mUdpSocketHandle >= 0) { + aoo::socket_close(mUdpSocketHandle); + } + //mUdpSocket.reset(); mAooDummySource.reset(); @@ -973,9 +1094,16 @@ void SonobusAudioProcessor::startAooServer() { const ScopedWriteLock sl (mCoreLock); int32_t err; - mAooServer.reset(aoo::net::iserver::create(10999, &err)); - - if (err != 0) { + mAooServer.reset(aoo::net::server::create(LOCAL_SERVER_PORT, 0, &err)); + + if (mAooServer) { + mAooServer->set_eventhandler( + [](void *user, const aoo_event *event, int32_t level) { + static_cast(user)->handleAooServerEvent(event, level); + }, this, AOO_EVENT_CALLBACK); + + } + else { DBG("Error creating Aoo Server: " << err); } } @@ -991,7 +1119,9 @@ void SonobusAudioProcessor::stopAooServer() if (mAooServer) { DBG("waiting on recv thread to die"); mAooServer->quit(); - mServerThread->stopThread(400); + if (mServerThread) { + mServerThread->stopThread(400); + } const ScopedWriteLock sl (mCoreLock); mAooServer.reset(); @@ -1014,19 +1144,48 @@ bool SonobusAudioProcessor::connectToServer(const String & host, int port, const // disconnect from everything else! removeAllRemotePeers(); - mServerEndpoint->ipaddr = host; - mServerEndpoint->port = port; - mServerEndpoint->peer.reset(); + //mServerEndpoint->ipaddr = host; + //mServerEndpoint->port = port; + //mServerEndpoint->add.reset(); + + + + auto cb = [](void *x, aoo_error result, const void *data){ + auto obj = (SonobusAudioProcessor *)x; + //auto obj = request->obj; + //auto group = request->group; + //auto pwd = request->pwd; + + if (result == AOO_OK){ + auto reply = (const aoo_net_connect_reply *)data; + auto user_id = reply->user_id; + + obj->mIsConnectedToServer = true; + obj->mSessionConnectionStamp = Time::getMillisecondCounterHiRes(); + + obj->clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientConnected, obj, result == AOO_OK, ""); + } else { + auto reply = (const aoo_net_error_reply *)data; + + obj->mIsConnectedToServer = false; + obj->mSessionConnectionStamp = 0.0; + + DBG("Error connecting to server: " << reply->error_code << " msg: " << reply->error_message); + + obj->clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientConnected, obj, result == AOO_OK, reply->error_message); + } + + }; + + int32_t retval = mAooClient->connect(host.toRawUTF8(), port, username.toRawUTF8(), passwd.toRawUTF8(), cb, this); mCurrentUsername = username; - int32_t retval = mAooClient->connect(host.toRawUTF8(), port, username.toRawUTF8(), passwd.toRawUTF8()); - - if (retval < 0) { + if (retval != AOO_OK) { DBG("Error connecting to server: " << retval); } - - return retval >= 0; + + return retval == AOO_OK; } bool SonobusAudioProcessor::isConnectedToServer() const @@ -1039,26 +1198,36 @@ bool SonobusAudioProcessor::isConnectedToServer() const bool SonobusAudioProcessor::disconnectFromServer() { if (!mAooClient) return false; - - mAooClient->disconnect(); - - // disconnect from everything else! - removeAllRemotePeers(); - { - const ScopedLock sl (mClientLock); + auto cb = [](void *y, aoo_error result, const void *data){ + auto x = (SonobusAudioProcessor *)y; + if (result == AOO_OK){ - mIsConnectedToServer = false; - mSessionConnectionStamp = 0.0; + } else { + auto reply = (const aoo_net_error_reply *)data; + DBG("Error disconnecting to server: " << reply->error_code << " msg: " << reply->error_message); + } - mCurrentJoinedGroup.clear(); - } + // disconnect from everything else! + x->removeAllRemotePeers(); - { - const ScopedLock sl (mPublicGroupsLock); + { + const ScopedLock sl (x->mClientLock); - mPublicGroupInfos.clear(); - } + x->mIsConnectedToServer = false; + x->mSessionConnectionStamp = 0.0; + + x->mCurrentJoinedGroup.clear(); + } + + { + const ScopedLock sl (x->mPublicGroupsLock); + + x->mPublicGroupInfos.clear(); + } + }; + + mAooClient->disconnect(cb, this); @@ -1133,8 +1302,9 @@ bool SonobusAudioProcessor::setWatchPublicGroups(bool flag) mWatchPublicGroups = flag; - int32_t retval = mAooClient->group_watch_public(flag); + JUCE_COMPILER_WARNING ("IMPLEMENT PUBLIC GROUPS"); + int32_t retval = 0; // mAooClient->group_watch_public(flag); const ScopedLock sl (mPublicGroupsLock); mPublicGroupInfos.clear(); @@ -1148,13 +1318,48 @@ bool SonobusAudioProcessor::setWatchPublicGroups(bool flag) } +struct GroupRequest { + SonobusAudioProcessor * obj; + String group; + bool ispublic; +}; bool SonobusAudioProcessor::joinServerGroup(const String & group, const String & groupsecret, bool isPublic) { if (!mAooClient) return false; - int32_t retval = mAooClient->group_join(group.toRawUTF8(), groupsecret.toRawUTF8(), isPublic); - + auto cb = [](void *x, aoo_error result, const void *data){ + auto request = (GroupRequest *)x; + auto obj = request->obj; + auto group = request->group; + auto reply = (const aoo_net_error_reply *)data; + + if (result == AOO_OK){ + DBG("Joined group - " << group); + + const ScopedLock sl (obj->mClientLock); + obj->mCurrentJoinedGroup = group; //CharPointer_UTF8 (e->name); + + obj->mSessionConnectionStamp = Time::getMillisecondCounterHiRes(); + } else { + //t_error_reply error { reply->error_code, reply->error_message }; + + DBG("Error joining group " << group << " : " << result); + + } + + obj->clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientGroupJoined, obj, result == AOO_OK, group, reply ? reply->error_message : ""); + + + delete request; + }; + + // need to add PUBLIC + int32_t retval = mAooClient->join_group(group.toRawUTF8(), groupsecret.toRawUTF8(), + cb, new GroupRequest { this, group, isPublic }); + + + if (retval < 0) { DBG("Error joining group " << group << " : " << retval); } @@ -1166,8 +1371,34 @@ bool SonobusAudioProcessor::leaveServerGroup(const String & group) { if (!mAooClient) return false; - int32_t retval = mAooClient->group_leave(group.toRawUTF8()); - + auto cb = [](void *x, aoo_error result, const void *data){ + auto request = (GroupRequest *)x; + auto obj = request->obj; + auto group = request->group; + auto reply = (const aoo_net_error_reply *)data; + + if (result == AOO_OK){ + DBG("Group leave - " << group); + + const ScopedLock sl (obj->mClientLock); + obj->mCurrentJoinedGroup.clear(); + + // assume they are all part of the group, XXX + obj->removeAllRemotePeers(); + + } else { + //t_error_reply error { reply->error_code, reply->error_message }; + DBG("Error leaving group " << group << " : " << (reply ? reply->error_message : "")); + + } + + obj->clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientGroupLeft, obj, result == AOO_OK, group, reply ? reply->error_message : ""); + + delete request; + }; + + int32_t retval = mAooClient->leave_group(group.toRawUTF8(), cb, new GroupRequest { this, group, false }); + if (retval < 0) { DBG("Error leaving group " << group << " : " << retval); } @@ -1343,7 +1574,8 @@ bool SonobusAudioProcessor::setRequestRemotePeerSendAudioCodecFormat(int index, const AudioCodecFormatInfo & info = mAudioFormats.getReference(formatIndex); if (formatInfoToAooFormat(info, remote->recvChannels, fmt)) { - remote->oursink->request_source_codec_change(remote->endpoint, remote->remoteSourceId, fmt.header); + JUCE_COMPILER_WARNING ("IMPLEMENT SOURCE CODEC CHANGE"); + //remote->oursink->request_source_codec_change(remote->endpoint, remote->remoteSourceId, fmt.header); remote->reqRemoteSendFormatIndex = formatIndex; return true; @@ -2040,45 +2272,52 @@ foleys::LevelMeterSource * SonobusAudioProcessor::getRemotePeerSendMeterSource(i -SonobusAudioProcessor::EndpointState * SonobusAudioProcessor::findOrAddRawEndpoint(void * rawaddr) +SonobusAudioProcessor::EndpointState * SonobusAudioProcessor::findOrAddRawEndpoint(const void * rawaddr, int addrlen) { - String ipaddr; - int port = 0 ; + aoo::ip_address addr((const struct sockaddr *)rawaddr, addrlen); - char hostip[INET6_ADDRSTRLEN]; - if (inet_ntop(AF_INET, get_in_addr((struct sockaddr *)rawaddr), hostip, sizeof(hostip)) == nullptr) { - DBG("Error converting raw addr to IP"); - return nullptr; - } else { - ipaddr = hostip; - port = ntohs(get_in_port((struct sockaddr *)rawaddr)); - return findOrAddEndpoint(ipaddr, port); - } + return findOrAddEndpoint(addr); } + SonobusAudioProcessor::EndpointState * SonobusAudioProcessor::findOrAddEndpoint(const String & host, int port) { - const ScopedLock sl (mEndpointsLock); - - EndpointState * endpoint = 0; - + aoo::ip_address addr(host.toStdString(), port); + + return findOrAddEndpoint(addr); +} + +SonobusAudioProcessor::EndpointState * SonobusAudioProcessor::findOrAddEndpoint(const aoo::ip_address & ipaddr) +{ + EndpointState * endpoint = findEndpoint(ipaddr); + + if (!endpoint) { + // add it as new + const ScopedLock sl (mEndpointsLock); + endpoint = mEndpoints.add(new EndpointState(ipaddr)); + endpoint->ownerSocket = mUdpSocketHandle; + DBG("Added new endpoint for " << ipaddr.name() << ":" << ipaddr.port()); + } + return endpoint; +} + +SonobusAudioProcessor::EndpointState * SonobusAudioProcessor::findEndpoint(const aoo::ip_address & ipaddr) +{ + const ScopedLock sl (mEndpointsLock); + + EndpointState * endpoint = nullptr; + for (auto ep : mEndpoints) { - if (ep->ipaddr == host && ep->port == port) { + if (ep->address == ipaddr) { endpoint = ep; break; } } - - if (!endpoint) { - // add it as new - endpoint = mEndpoints.add(new EndpointState(host, port)); - endpoint->owner = mUdpSocket.get(); - endpoint->peer = std::make_unique(host, port); - DBG("Added new endpoint for " << host << ":" << port); - } + return endpoint; } + void SonobusAudioProcessor::updateSafetyMuting(RemotePeer * peer) { // assumed corelock already held @@ -2114,27 +2353,71 @@ void SonobusAudioProcessor::doReceiveData() { // receive from udp port, and parse packet char buf[AOO_MAXPACKETSIZE]; - String senderIP; - int senderPort; - - int nbytes = mUdpSocket->read(buf, AOO_MAXPACKETSIZE, false, senderIP, senderPort); - if (nbytes == 0) return; + //int nbytes = mUdpSocket->read(buf, AOO_MAXPACKETSIZE, false, senderIP, senderPort); + + aoo::ip_address addr; + int32_t addrlen = aoo::ip_address::max_length; + int32_t timeoutusec = 20000; // 20 ms + //int nbytes = mUdpSocket->read(buf, AOO_MAXPACKETSIZE, false, addr.address_ptr(), addrlen); + int nbytes = socket_receive(mUdpSocketHandle, buf, AOO_MAXPACKETSIZE, &addr, timeoutusec); + + if (nbytes == 0) return; // timeout else if (nbytes < 0) { DBG("Error receiving UDP"); return; } - + + //*addr.length_ptr() = addrlen; + // find endpoint from sender info - EndpointState * endpoint = findOrAddEndpoint(senderIP, senderPort); + EndpointState * endpoint = findOrAddEndpoint(addr); endpoint->recvBytes += nbytes + UDP_OVERHEAD_BYTES; - + + // TODO - handle possible OSC bundles + bool aoohandled = false; + + if (mAooClient) { + // AoO message + const ScopedReadLock sl (mCoreLock); + + aoohandled = mAooClient->handle_message(buf, nbytes, addr.address(), addr.length()) == AOO_OK; + + if (aoohandled) { + + if (auto * remote = findRemotePeer(endpoint, -1)) { + // todo - remote->dataPacketsReceived += 1; + if (remote->recvAllow && !remote->recvActive) { + remote->recvActive = true; + } + if (remote->resetSafetyMuted) { + updateSafetyMuting(remote); + } + } + + // notify send thread + notifySendThread(); + } + } + + if (!aoohandled) { + if (handleOtherMessage(endpoint, buf, nbytes)) { + + } + else { + // not a valid AoO OSC message + DBG("SonoBus: not a valid AOO message! : " << buf[0] << buf[1] << buf[2] << buf[3]); + } + } + +#if 0 // parse packet for AOO events int32_t type, id, dummyid; - if ((aoo_parse_pattern(buf, nbytes, &type, &id) > 0) - || (aoonet_parse_pattern(buf, nbytes, &type) > 0)) + int32_t onset; + if ((aoo_parse_pattern(buf, nbytes, &type, &id, &onset) > 0) + || (aoo_parse_pattern(buf, nbytes, &type, &id, &onset) > 0)) { { @@ -2159,7 +2442,7 @@ void SonobusAudioProcessor::doReceiveData() } } - if (id == AOO_ID_WILDCARD || (remote->oursink->get_id(dummyid) && id == dummyid) ) { + if (id == AOO_ID_WILDCARD || (remote->oursink->get_id(dummyid) == AOO_OK && id == dummyid) ) { if (remote->oursink->handle_message(buf, nbytes, endpoint, endpoint_send)) { remote->dataPacketsReceived += 1; if (remote->recvAllow && !remote->recvActive) { @@ -2173,11 +2456,11 @@ void SonobusAudioProcessor::doReceiveData() if (id != AOO_ID_WILDCARD) break; } - if (remote->echosink->get_id(dummyid) && id == dummyid) { + if (remote->echosink->get_id(dummyid) == AOO_OK && id == dummyid) { remote->echosink->handle_message(buf, nbytes, endpoint, endpoint_send); break; } - else if (remote->latencysink->get_id(dummyid) && id == dummyid) { + else if (remote->latencysink->get_id(dummyid) == AOO_OK && id == dummyid) { remote->latencysink->handle_message(buf, nbytes, endpoint, endpoint_send); break; } @@ -2189,7 +2472,7 @@ void SonobusAudioProcessor::doReceiveData() const ScopedReadLock sl (mCoreLock); - if (mAooDummySource->get_id(dummyid) && id == dummyid) { + if (mAooDummySource->get_id(dummyid) == AOO_OK && id == dummyid) { // this is the special one that can accept blind invites mAooDummySource->handle_message(buf, nbytes, endpoint, endpoint_send); } @@ -2201,11 +2484,11 @@ void SonobusAudioProcessor::doReceiveData() if (id != AOO_ID_WILDCARD) break; } - if (remote->echosource->get_id(dummyid) && id == dummyid) { + if (remote->echosource->get_id(dummyid) == AOO_OK && id == dummyid) { remote->echosource->handle_message(buf, nbytes, endpoint, endpoint_send); break; } - else if (remote->latencysource->get_id(dummyid) && id == dummyid) { + else if (remote->latencysource->get_id(dummyid) == AOO_OK && id == dummyid) { remote->latencysource->handle_message(buf, nbytes, endpoint, endpoint_send); break; } @@ -2257,7 +2540,8 @@ void SonobusAudioProcessor::doReceiveData() // not a valid AoO OSC message DBG("SonoBus: not a valid AOO message!"); } - +#endif + } // XXX @@ -2404,7 +2688,7 @@ bool SonobusAudioProcessor::handleOtherMessage(EndpointState * endpoint, const c auto tt = (it++)->AsTimeTag(); // now prepare and send ack immediately - auto tt2 = aoo_osctime_get(); // use real system time + auto tt2 = aoo_osctime_now(); // use real system time char buf[AOO_MAXPACKETSIZE]; osc::OutboundPacketStream outmsg(buf, sizeof(buf)); @@ -2419,7 +2703,11 @@ bool SonobusAudioProcessor::handleOtherMessage(EndpointState * endpoint, const c return false; } - endpoint_send(endpoint, outmsg.Data(), (int) outmsg.Size()); + if (mAooClient) { + mAooClient->send_message(outmsg.Data(), (int) outmsg.Size(), endpoint->address.address(), endpoint->address.length(), 0); + } else { + endpoint_send(endpoint, outmsg.Data(), (int) outmsg.Size()); + } DBG("Received ping from " << endpoint->ipaddr << ":" << endpoint->port << " stamp: " << tt); @@ -2431,7 +2719,7 @@ bool SonobusAudioProcessor::handleOtherMessage(EndpointState * endpoint, const c auto it = message.ArgumentsBegin(); auto tt = (it++)->AsTimeTag(); auto tt2 = (it++)->AsTimeTag(); - auto tt3 = aoo_osctime_get(); // use real system time + auto tt3 = aoo_osctime_now(); // use real system time handlePingEvent(endpoint, tt, tt2, tt3); // jlc @@ -2461,7 +2749,7 @@ bool SonobusAudioProcessor::handleOtherMessage(EndpointState * endpoint, const c // find remote peer RemotePeer * peer = findRemotePeer(endpoint, -1); if (!peer) { - DBG("Could not find peer for endpoint"); + DBG("Peerinfo: Could not find peer for endpoint: " << endpoint->ipaddr << " port: " << endpoint->port); return false; } @@ -2499,7 +2787,7 @@ bool SonobusAudioProcessor::handleOtherMessage(EndpointState * endpoint, const c // find remote peer RemotePeer * peer = findRemotePeer(endpoint, sourceid); if (!peer) { - DBG("Could not find peer for endpoint: " << endpoint->ipaddr << "src: " << sourceid); + DBG("layoutinfo: Could not find peer for endpoint: " << endpoint->ipaddr << " src: " << sourceid); } else { peer->recvdChanLayout = true; @@ -2859,166 +3147,1144 @@ void SonobusAudioProcessor::sendRemotePeerInfoUpdate(int index, RemotePeer * top // nettype TODO - char buf[AOO_MAXPACKETSIZE]; + char buf[AOO_MAXPACKETSIZE]; + + const ScopedReadLock sl (mCoreLock); + for (int i=0; i < mRemotePeers.size(); ++i) { + auto * peer = mRemotePeers.getUnchecked(i); + if (topeer && topeer != peer) continue; + if (index >= 0 && index != i) continue; + + osc::OutboundPacketStream msg(buf, sizeof(buf)); + + auto buftimeMs = jmax((double)peer->buffertimeMs, 1e3 * currSamplesPerBlock / getSampleRate()); + info->setProperty("jitbuf", buftimeMs); + + String jsonstr = JSON::toString(info.get(), true, 6); + + if (jsonstr.getNumBytesAsUTF8() > AOO_MAXPACKETSIZE - 100) { + DBG("Info too big for packet!"); + return; + } + + try { + msg << osc::BeginMessage(SONOBUS_FULLMSG_PEERINFO) + << osc::Blob(jsonstr.toRawUTF8(), (int) jsonstr.getNumBytesAsUTF8()) + << osc::EndMessage; + } + catch (const osc::Exception& e){ + DBG("exception in PEERINFO message constructions: " << e.what()); + continue; + } + + DBG("Sending peerinfo message to " << i); + this->sendPeerMessage(peer, msg.Data(), (int32_t) msg.Size()); + + if (index == i || topeer == peer) break; + } + +} + + +int32_t SonobusAudioProcessor::sendPeerMessage(RemotePeer * peer, const char *msg, int32_t n) +{ + if (mAooClient) { + mAooClient->send_message(msg, n, peer->endpoint->address.address(), peer->endpoint->address.length(), 0); + } else { + return endpoint_send(peer->endpoint, msg, n); + } +} + + + + +void SonobusAudioProcessor::doSendData() +{ + // just try to send for everybody + const ScopedReadLock sl (mCoreLock); + + // send stuff until there is nothing left to send + + int32_t didsomething = 1; + + auto nowtimems = Time::getMillisecondCounterHiRes(); + + + if (mAooClient) { + mAooClient->send(udpsend, this); + } + + + for (auto & remote : mRemotePeers) { + if ( nowtimems > (remote->lastSendPingTimeMs + PEER_PING_INTERVAL_MS) ) { + sendPingEvent(remote); + remote->lastSendPingTimeMs = nowtimems; + if (!remote->haveSentFirstPeerInfo) { + sendRemotePeerInfoUpdate(-1, remote); + remote->haveSentFirstPeerInfo = true; + } + } + } + +#if 0 + while (didsomething) { + //mAooSource->send(); + didsomething = 0; + + didsomething |= mAooDummySource->send(); + + if (mAooClient) { + mAooClient->send(); + } + + for (auto & remote : mRemotePeers) { + if (remote->oursource) { + didsomething |= remote->oursource->send(); + + if (didsomething) { + remote->dataPacketsSent += 1; + } + } + if (remote->oursink) { + didsomething |= remote->oursink->send(); + } + + if (remote->latencysource) { + didsomething |= remote->latencysource->send(); + didsomething |= remote->latencysink->send(); + didsomething |= remote->echosource->send(); + didsomething |= remote->echosink->send(); + } + + if ( nowtimems > (remote->lastSendPingTimeMs + PEER_PING_INTERVAL_MS) ) { + sendPingEvent(remote); + remote->lastSendPingTimeMs = nowtimems; + if (!remote->haveSentFirstPeerInfo) { + sendRemotePeerInfoUpdate(-1, remote); + remote->haveSentFirstPeerInfo = true; + } + } + } + } +#endif + + + if (mPendingUnmute.get() && mPendingUnmuteAtStamp < Time::getMillisecondCounter() ) { + DBG("UNMUTING ALL"); + mState.getParameter(paramMainRecvMute)->setValueNotifyingHost(0.0f); + + mPendingUnmute = false; + } + + if (mNeedsSampleSetup.get()) { + DBG("Doing sample setup for all"); + setupSourceFormatsForAll(); + mNeedsSampleSetup = false; + + // reset all incoming by toggling muting + if (!mMainRecvMute.get()) { + DBG("Toggling main recv mute"); + mState.getParameter(paramMainRecvMute)->setValueNotifyingHost(1.0f); + mPendingUnmuteAtStamp = Time::getMillisecondCounter() + 250; + mPendingUnmute = true; + } + + sendRemotePeerInfoUpdate(-1); // send to all + + } + + +} + + +#if 0 +static int32_t gHandleSourceEvents(void * user, const aoo_event ** events, int32_t n) +{ + ProcessorIdPair * pp = static_cast (user); + return pp->processor->handleSourceEvents(events, n, pp->id); +} + +static int32_t gHandleSinkEvents(void * user, const aoo_event ** events, int32_t n) +{ + ProcessorIdPair * pp = static_cast (user); + return pp->processor->handleSinkEvents(events, n, pp->id); +} + +static int32_t gHandleServerEvents(void * user, const aoo_event ** events, int32_t n) +{ + ProcessorIdPair * pp = static_cast (user); + return pp->processor->handleServerEvents(events, n); +} + +static int32_t gHandleClientEvents(void * user, const aoo_event ** events, int32_t n) +{ + ProcessorIdPair * pp = static_cast (user); + return pp->processor->handleClientEvents(events, n); +} +#endif + + +int32_t SonobusAudioProcessor::handleAooServerEvent(const aoo_event *event, int32_t level) +{ + switch (event->type){ + case AOO_NET_USER_JOIN_EVENT: + { + auto e = (const aoo_net_user_event *)event; + + DBG("Server - User joined: " << e->user_name); + + break; + } + case AOO_NET_USER_LEAVE_EVENT: + { + auto e = (const aoo_net_user_event *)event; + + DBG("Server - User left: " << e->user_name); + + + break; + } + case AOO_NET_GROUP_JOIN_EVENT: + { + auto e = (const aoo_net_group_event *)event; + + DBG("Server - Group Joined: " << e->group_name << " by user: " << e->user_name); + + break; + } + case AOO_NET_GROUP_LEAVE_EVENT: + { + auto e = (const aoo_net_group_event *)event; + + DBG("Server - Group Left: " << e->group_name << " by user: " << e->user_name); + + break; + } + case AOO_NET_ERROR_EVENT: + { + auto e = (const aoo_net_error_event *)event; + + DBG("Server error: " << e->error_message); + + break; + } + default: + DBG("Got unknown server event: " << event->type); + break; + } + + return AOO_OK; +} + +int32_t SonobusAudioProcessor::handleAooClientEvent(const aoo_event *event, int32_t level) +{ + switch (event->type){ + /* + case AOONET_CLIENT_CONNECT_EVENT: + { + aoonet_client_group_event *e = (aoonet_client_group_event *)event; + if (e->result > 0){ + DBG("Connected to server!"); + mIsConnectedToServer = true; + mSessionConnectionStamp = Time::getMillisecondCounterHiRes(); + } else { + DBG("Couldn't connect to server - " << e->errormsg); + mIsConnectedToServer = false; + mSessionConnectionStamp = 0.0; + } + + clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientConnected, this, e->result > 0, e->errormsg); + + break; + } + */ + + case AOO_NET_PEER_MESSAGE_EVENT: + { + auto e = (const aoo_net_message_event *)event; + + aoo::ip_address address((const sockaddr *)e->address, e->addrlen); + EndpointState * es = (EndpointState *) findOrAddEndpoint(address); + + handleOtherMessage(es, e->data, e->size); + + break; + } + + + case AOO_NET_DISCONNECT_EVENT: + { + // don't remove all peers? + //removeAllRemotePeers(); + + mIsConnectedToServer = false; + mSessionConnectionStamp = 0.0; + + clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientDisconnected, this, true, ""); + + break; + } +#if 0 + case AOONET_CLIENT_GROUP_JOIN_EVENT: + { + aoonet_client_group_event *e = (aoonet_client_group_event *)event; + if (e->result > 0){ + DBG("Joined group - " << e->name); + const ScopedLock sl (mClientLock); + mCurrentJoinedGroup = CharPointer_UTF8 (e->name); + + mSessionConnectionStamp = Time::getMillisecondCounterHiRes(); + + + } else { + DBG("Couldn't join group " << e->name << " - " << e->errormsg); + } + clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientGroupJoined, this, e->result > 0, CharPointer_UTF8 (e->name), e->errormsg); + break; + } + case AOONET_CLIENT_GROUP_LEAVE_EVENT: + { + aoonet_client_group_event *e = (aoonet_client_group_event *)event; + if (e->result > 0){ + + DBG("Group leave - " << e->name); + + const ScopedLock sl (mClientLock); + mCurrentJoinedGroup.clear(); + + // assume they are all part of the group, XXX + removeAllRemotePeers(); + + //aoo_node_remove_group(x->x_node, gensym(e->name)); + + + } else { + DBG("Couldn't leave group " << e->name << " - " << e->errormsg); + } + + clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientGroupLeft, this, e->result > 0, CharPointer_UTF8 (e->name), e->errormsg); + + break; + } + case AOONET_CLIENT_GROUP_PUBLIC_ADD_EVENT: + { + aoonet_client_group_event *e = (aoonet_client_group_event *)event; + DBG("Public group add/changed - " << e->name << " count: " << e->result); + { + const ScopedLock sl (mPublicGroupsLock); + String group = CharPointer_UTF8 (e->name); + AooPublicGroupInfo & ginfo = mPublicGroupInfos[group]; + ginfo.groupName = group; + ginfo.activeCount = e->result; + ginfo.timestamp = Time::getCurrentTime().toMilliseconds(); + } + + clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientPublicGroupModified, this, CharPointer_UTF8 (e->name), e->result, e->errormsg); + break; + } + case AOONET_CLIENT_GROUP_PUBLIC_DEL_EVENT: + { + aoonet_client_group_event *e = (aoonet_client_group_event *)event; + DBG("Public group deleted - " << e->name); + { + const ScopedLock sl (mPublicGroupsLock); + String group = CharPointer_UTF8 (e->name); + mPublicGroupInfos.erase(group); + } + + clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientPublicGroupDeleted, this, CharPointer_UTF8 (e->name), e->errormsg); + break; + } +#endif + + + case AOO_NET_PEER_HANDSHAKE_EVENT: + { + auto e = (const aoo_net_peer_event *)event; + + DBG("Peer attempting to join group " << e->group_name << " - user " << e->user_name); + + clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientPeerPendingJoin, this, CharPointer_UTF8 (e->group_name), CharPointer_UTF8 (e->user_name)); + + break; + } + + case AOO_NET_PEER_JOIN_EVENT: + { + auto e = (const aoo_net_peer_event *)event; + + DBG("Peer joined group " << e->group_name << " - user " << e->user_name); + + if (mAutoconnectGroupPeers) { + connectRemotePeerRaw(e->address, e->addrlen, CharPointer_UTF8 (e->user_name), CharPointer_UTF8 (e->group_name), !mMainRecvMute.get()); + } + + //aoo_node_add_peer(x->x_node, gensym(e->group), gensym(e->user), + // (const struct sockaddr *)e->address, e->length); + + clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientPeerJoined, this, CharPointer_UTF8 (e->group_name), CharPointer_UTF8 (e->user_name)); + + + break; + } + case AOO_NET_PEER_TIMEOUT_EVENT: + { + auto e = (const aoo_net_peer_event *)event; + + DBG("Peer failed to join group " << e->group_name << " - user " << e->user_name); + + clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientPeerJoinFailed, this, CharPointer_UTF8 (e->group_name), CharPointer_UTF8 (e->user_name)); + + break; + } + case AOO_NET_PEER_LEAVE_EVENT: + { + auto e = (const aoo_net_peer_event *)event; + + DBG("Peer leave group " << e->group_name << " - user " << e->user_name); + + EndpointState * endpoint = findOrAddRawEndpoint(e->address, e->addrlen); + if (endpoint) { + + removeAllRemotePeersWithEndpoint(endpoint); + } + + //aoo_node_remove_peer(x->x_node, gensym(e->group), gensym(e->user)); + clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientPeerLeft, this, CharPointer_UTF8 (e->group_name), CharPointer_UTF8 (e->user_name)); + + break; + } + case AOO_NET_ERROR_EVENT: + { + auto e = (const aoo_net_error_event *)event; + DBG("client error: " << e->error_message); + clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientError, this, e->error_message); + break; + } + default: + DBG("Got unknown client event: " << event->type); + break; + } + + return AOO_OK; +} + + +int32_t SonobusAudioProcessor::handleAooSinkEvent(const aoo_event *event, int32_t level, int32 sinkId) +{ + switch (event->type){ + case AOO_SOURCE_ADD_EVENT: + { + auto e = (aoo_source_event *)event; + EndpointState * es = (EndpointState *) findOrAddRawEndpoint(e->ep.address, e->ep.addrlen); + + RemotePeer * peer = findRemotePeer(es, sinkId); + if (peer) { + // someone has added us, thus accepting our invitation + int32_t dummyid; + + if (mAooDummySource->get_id(dummyid) == AOO_OK && dummyid == e->ep.id ) { + // ignoring dummy add + DBG("Got dummy handshake add from " << es->ipaddr << ":" << es->port); + } + else { + DBG("Added source " << es->ipaddr << ":" << es->port << " " << e->ep.id << " to our " << sinkId); + peer->remoteSourceId = e->ep.id; + + aoo_endpoint bogusep = { e->ep.address, e->ep.addrlen, 0 }; + peer->oursink->uninvite_source(bogusep); // get rid of existing bogus one + + if (peer->recvAllow) { + peer->oursink->invite_source(e->ep, peer->aooPeerInfo.flags); + //peer->recvActive = true; + } else { + DBG("we aren't accepting recv right now, politely decline it"); + peer->oursink->uninvite_source(e->ep); + peer->recvActive = false; + } + + peer->connected = true; + + + + } + + // do invite here? + + } + else { + DBG("Added source to unknown " << e->ep.id); + } + // add remote source + //doAddRemoteSourceIfNecessary(es, e->id); + + + break; + } + case AOO_FORMAT_CHANGE_EVENT: + { + auto e = (const aoo_format_change_event *)event; + EndpointState * es = (EndpointState *) findOrAddRawEndpoint(e->ep.address, e->ep.addrlen); + + const ScopedReadLock sl (mCoreLock); + + RemotePeer * peer = findRemotePeer(es, sinkId); + if (peer) { + aoo_format_storage f; + if (peer->oursink->get_source_format(e->ep, f) == AOO_OK) { + DBG("Got source format event from " << es->ipaddr << ":" << es->port << " " << e->ep.id << " channels: " << f.header.nchannels); + peer->recvMeterSource.resize(f.header.nchannels, meterRmsWindow); + + // check for layout + bool gotuserformat = false; + char userfmtdata[1024]; + JUCE_COMPILER_WARNING ("TODO: USERFORMAT"); + /* + int32_t retsize = peer->oursink->get_sourceoption(e->endpoint, e->id, aoo_opt_userformat, userfmtdata, sizeof(userfmtdata)); + if (retsize > 0) { + ValueTree tree = ValueTree::readFromData (userfmtdata, retsize); + + if (tree.isValid()) { + applyLayoutFormatToPeer(peer, tree); + gotuserformat = true; + } + else { + DBG("Error parsing userformat"); + } + } + else { + DBG("No userformat: " << retsize); + } + */ + + if (peer->recvChannels != f.header.nchannels) { + + { + const ScopedWriteLock sl (peer->sinkLock); + + peer->recvChannels = std::min(MAX_PANNERS, f.header.nchannels); + + // set up this sink with new channel count + + int sinkchan = std::max(getMainBusNumOutputChannels(), peer->recvChannels); + + peer->oursink->setup(getSampleRate(), currSamplesPerBlock, sinkchan); + } + peer->recvMeterSource.resize (peer->recvChannels, meterRmsWindow); + + // for now if > 2, all on own changroup (by default) + + if (!gotuserformat && !peer->recvdChanLayout) { + if (peer->recvChannels > 2) { + if (!peer->modifiedChanGroups) { + for (int cgi=0; cgi < peer->recvChannels; ++cgi) { + peer->chanGroups[cgi].params.chanStartIndex = cgi; + peer->chanGroups[cgi].params.numChannels = 1; + } + peer->numChanGroups = peer->recvChannels; + } + } + else { + peer->chanGroups[0].params.numChannels = peer->recvChannels; + peer->numChanGroups = 1; + + if (peer->recvChannels == 1) { + peer->viewExpanded = false; + } + } + } + + if (peer->recvChannels == 1) { + peer->viewExpanded = false; + } + + /* + if (peer->recvChannels == 1) { + // center pan + peer->recvPan[0] = 0.0f; + } else if (peer->recvChannels == 2) { + // Left/Right + peer->recvStereoPan[0] = -1.0f; + peer->recvStereoPan[1] = 1.0f; + } else if (peer->recvChannels > 2) { + peer->recvStereoPan[0] = -1.0f; + peer->recvStereoPan[1] = 1.0f; + for (int i=2; i < peer->recvChannels; ++i) { + peer->recvPan[i] = 0.0f; + } + } + */ + } + + + + AudioCodecFormatCodec codec = String(f.header.codec) == AOO_CODEC_OPUS ? CodecOpus : CodecPCM; + if (codec == CodecOpus) { + aoo_format_opus *fmt = (aoo_format_opus *)&f; + peer->recvFormat = AudioCodecFormatInfo(fmt->bitrate/fmt->header.nchannels, fmt->complexity, fmt->signal_type); + //peer->recvFormatIndex = findFormatIndex(codec, fmt->bitrate / fmt->header.nchannels, 0); + } else { + aoo_format_pcm *fmt = (aoo_format_pcm *)&f; + int bitdepth = fmt->bitdepth == AOO_PCM_INT16 ? 2 : fmt->bitdepth == AOO_PCM_INT24 ? 3 : fmt->bitdepth == AOO_PCM_FLOAT32 ? 4 : fmt->bitdepth == AOO_PCM_FLOAT64 ? 8 : 2; + peer->recvFormat = AudioCodecFormatInfo(bitdepth); + //peer->recvFormatIndex = findFormatIndex(codec, 0, fmt->bitdepth); + } + + clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientPeerChangedState, this, "format"); + } + } + else { + DBG("format event to unknown " << e->ep.id); + + } + + break; + } + case AOO_STREAM_STATE_EVENT: + { + auto e = (aoo_stream_state_event *)event; + EndpointState * es = (EndpointState *) findOrAddRawEndpoint(e->ep.address, e->ep.addrlen); + + DBG("Got source state event from " << es->ipaddr << ":" << es->port << " -- " << e->state); + + const ScopedReadLock sl (mCoreLock); + + RemotePeer * peer = findRemotePeer(es, sinkId); + if (peer) { + peer->recvActive = peer->recvAllow && e->state > 0; + if (!peer->recvActive && !peer->sendActive) { + peer->connected = false; + } else { + peer->connected = true; + } + } + + //clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientPeerChangedState, this, "state"); + + break; + } + case AOO_BUFFER_UNDERRUN_EVENT: + //case AOO_BLOCK_LOST_EVENT: + { + aoo_block_lost_event *e = (aoo_block_lost_event *)event; + + EndpointState * es = (EndpointState *) findOrAddRawEndpoint(e->ep.address, e->ep.addrlen); + + DBG("Got source underrun event from " << es->ipaddr << ":" << es->port << " " << e->ep.id << " -- " << e->count); + + const ScopedReadLock sl (mCoreLock); + RemotePeer * peer = findRemotePeer(es, sinkId); + if (peer) { + peer->dataPacketsDropped += 1; // e->count; + + if (peer->autosizeBufferMode != AutoNetBufferModeOff) { + // see if our drop rate exceeds threshold, and increase buffersize if so + double nowtime = Time::getMillisecondCounterHiRes(); + const float dropratethresh = peer->autosizeBufferMode == AutoNetBufferModeInitAuto ? 1.0f : mAutoresizeDropRateThresh; + const float adjustlimit = 0.5f; // don't adjust more often than once every 0.5 seconds + + bool autoinitdone = peer->autosizeBufferMode == AutoNetBufferModeInitAuto && peer->autoNetbufInitCompleted; + + if (peer->lastDroptime > 0 && !autoinitdone) { + double deltatime = (nowtime - peer->lastDroptime) * 1e-3; + if (deltatime > adjustlimit) { + //float droprate = (peer->dataPacketsDropped - peer->lastDropCount) / deltatime; + float droprate = 1.0f / deltatime; // treat any drops as one instance + if (droprate > dropratethresh) { + float adjms = 1000.0f * currSamplesPerBlock / getSampleRate(); + peer->buffertimeMs += adjms; + peer->totalEstLatency = peer->smoothPingTime.xbar + 2*peer->buffertimeMs + (1e3*currSamplesPerBlock/getSampleRate()); + peer->oursink->set_buffersize(peer->buffertimeMs); + peer->echosink->set_buffersize(peer->buffertimeMs); + peer->latencysink->set_buffersize(peer->buffertimeMs); + peer->latencyDirty = true; + peer->fillRatioSlow.reset(); + peer->fillRatio.reset(); + + DBG("AUTO-Increasing buffer time by " << adjms << " ms to " << (int)peer->buffertimeMs << " droprate: " << droprate); + + if (peer->hasRealLatency) { + peer->totalEstLatency = peer->totalLatency + (peer->buffertimeMs - peer->bufferTimeAtRealLatency); + } + + if (peer->autosizeBufferMode == AutoNetBufferModeAutoFull) { + + const float timesincedecrthresh = 2.0; + if (peer->lastNetBufDecrTime > 0 && (nowtime - peer->lastNetBufDecrTime)*1e-3 < timesincedecrthresh ) { + peer->netBufAutoBaseline = peer->buffertimeMs; + DBG("Got drop within short time thresh, setting minimum baseline for future decr to " << peer->netBufAutoBaseline); + } + } + + sendRemotePeerInfoUpdate(-1, peer); // send to this peer + + } + + float realdroprate = (peer->dataPacketsDropped - peer->lastDropCount) / deltatime; + peer->fastDropRate.push(realdroprate); + + peer->lastDroptime = nowtime; + peer->lastDropCount = peer->dataPacketsDropped; + } + } + else { + if (peer->lastDroptime > 0) { + double deltatime = (nowtime - peer->lastDroptime) * 1e-3; + float droprate = (peer->dataPacketsDropped - peer->lastDropCount) / deltatime; + peer->fastDropRate.push(droprate); + } + + peer->lastDroptime = nowtime; + peer->lastDropCount = peer->dataPacketsDropped; + } + + + + //peer->lastNetBufDecrTime = 0; // reset auto-decr + } + } + + break; + } + case AOO_BLOCK_REORDERED_EVENT: + { + aoo_block_reordered_event *e = (aoo_block_reordered_event *)event; + + EndpointState * es = (EndpointState *) findOrAddRawEndpoint(e->ep.address, e->ep.addrlen); + + DBG("Got source block reordered event from " << es->ipaddr << ":" << es->port << " " << e->ep.id << " -- " << e->count); + + break; + } + case AOO_BLOCK_DROPPED_EVENT: + { + aoo_block_dropped_event *e = (aoo_block_dropped_event *)event; + + EndpointState * es = (EndpointState *) findOrAddRawEndpoint(e->ep.address, e->ep.addrlen); + + DBG("Got source block dropped event from " << es->ipaddr << ":" << es->port << " " << e->ep.id << " -- " << e->count); + + break; + } + case AOO_BLOCK_RESENT_EVENT: + { + aoo_block_resent_event *e = (aoo_block_resent_event *)event; + EndpointState * es = (EndpointState *) findOrAddRawEndpoint(e->ep.address, e->ep.addrlen); + + DBG("Got source block resent event from " << es->ipaddr << ":" << es->port << " " << e->ep.id << " -- " << e->count); + const ScopedReadLock sl (mCoreLock); + RemotePeer * peer = findRemotePeer(es, sinkId); + if (peer) { + peer->dataPacketsResent += e->count; + } + + break; + } + case AOO_BLOCK_GAP_EVENT: + { + aoo_block_gap_event *e = (aoo_block_gap_event *)event; + + EndpointState * es = (EndpointState *) findOrAddRawEndpoint(e->ep.address, e->ep.addrlen); + + DBG("Got source block gap event from " << es->ipaddr << ":" << es->port << " " << e->ep.id << " -- " << e->count); + + break; + } + case AOO_PING_EVENT: + { + aoo_ping_event *e = (aoo_ping_event *)event; + EndpointState * es = (EndpointState *) findOrAddRawEndpoint(e->ep.address, e->ep.addrlen); + + + double diff = aoo_osctime_duration(e->tt1, e->tt2) * 1000.0; + DBG("Got source block ping event from " << es->ipaddr << ":" << es->port << " " << e->ep.id << " -- " << diff); + + + RemotePeer * peer = findRemotePeer(es, sinkId); + if (peer) { + const ScopedReadLock sl (mCoreLock); + + double nowtime = Time::getMillisecondCounterHiRes(); + + double deltadroptime = peer->lastDroptime > 0 ? (nowtime - peer->lastDroptime) * 1e-3 : (nowtime - peer->resetDroptime) * 1e-3; + + if (peer->autosizeBufferMode != AutoNetBufferModeOff) { + if (!peer->autoNetbufInitCompleted) { + const float nodropsthresh = 7.0; + + if (deltadroptime > nodropsthresh) { + peer->autoNetbufInitCompleted = true; + peer->resetSafetyMuted = false; + DBG("Netbuf Initial auto time is done after no drops in " << nodropsthresh); + + // clear drop count + peer->dataPacketsResent = 0; + peer->dataPacketsDropped = 0; + peer->lastDropCount = 0; + peer->resetDroptime = nowtime; + peer->fastDropRate.resetInitVal(0.0f); + //peer->lastDroptime = 0; + } + + } + + } + else { + // manual mode + peer->resetSafetyMuted = false; + } + + + if (peer->autosizeBufferMode == AutoNetBufferModeAutoFull) { + // possibly adjust net buffer down, if it has been longer than threshold since last drop + double nowtime = Time::getMillisecondCounterHiRes(); + const float nodropsthresh = 10.0; // no drops in 10 seconds + const float adjustlimit = 10; // don't adjust more often than once every 10 seconds + + if (peer->lastNetBufDecrTime > 0 && peer->buffertimeMs > peer->netBufAutoBaseline && !peer->latencyMatched) { + double deltatime = (nowtime - peer->lastNetBufDecrTime) * 1e-3; + double deltadroptime = (nowtime - peer->lastDroptime) * 1e-3; + if (deltatime > adjustlimit) { + //float droprate = (peer->dataPacketsDropped - peer->lastDropCount) / deltatime; + //if (droprate < dropratethresh) { + if (deltadroptime > nodropsthresh) { + float adjms = 1000.0f * currSamplesPerBlock / getSampleRate(); + peer->buffertimeMs -= adjms; + + peer->buffertimeMs = std::max(peer->buffertimeMs, peer->netBufAutoBaseline); + + peer->totalEstLatency = peer->smoothPingTime.xbar + 2*peer->buffertimeMs + (1e3*currSamplesPerBlock/getSampleRate()); + peer->oursink->set_buffersize(peer->buffertimeMs); + peer->echosink->set_buffersize(peer->buffertimeMs); + peer->latencysink->set_buffersize(peer->buffertimeMs); + peer->latencyDirty = true; + + peer->fillRatioSlow.reset(); + peer->fillRatio.reset(); + + if (peer->hasRealLatency) { + peer->totalEstLatency = peer->totalLatency + (peer->buffertimeMs - peer->bufferTimeAtRealLatency); + } + + DBG("AUTO-Decreasing buffer time by " << adjms << " ms to " << (int) peer->buffertimeMs); + + peer->lastNetBufDecrTime = nowtime; + + sendRemotePeerInfoUpdate(-1, peer); // send to this peer + + } + + //peer->lastNetBufDropCount = peer->dataPacketsDropped; + } + } + else { + peer->lastNetBufDecrTime = nowtime; + } + + } + + if (peer->resetSafetyMuted) { + updateSafetyMuting(peer); + } + } + + + break; + } + default: + break; + } + + return AOO_OK; +} + +int32_t SonobusAudioProcessor::handleAooSourceEvent(const aoo_event *event, int32_t level, int32_t sourceId) +{ + switch (event->type){ + case AOO_PING_EVENT: + { + aoo_ping_event *e = (aoo_ping_event *)event; + double diff1 = aoo_osctime_duration(e->tt1, e->tt2) * 1000.0; + double diff2 = aoo_osctime_duration(e->tt2, e->tt3) * 1000.0; + double rtt = aoo_osctime_duration(e->tt1, e->tt3) * 1000.0; + + EndpointState * es = (EndpointState *) findOrAddRawEndpoint(e->ep.address, e->ep.addrlen); + + RemotePeer * peer = findRemotePeer(es, sourceId); + if (peer && !peer->gotNewStylePing) { + const ScopedReadLock sl (mCoreLock); + + // smooth it + peer->pingTime = rtt; // * 0.5; + if (rtt < 600.0 ) { + peer->smoothPingTime.Z *= 0.5f; + peer->smoothPingTime.push(peer->pingTime); + } + + DBG("ping to source " << sourceId << " recvd from " << es->ipaddr << ":" << es->port << " -- " << diff1 << " " << diff2 << " " << rtt << " smooth: " << peer->smoothPingTime.xbar << " stdev: " <smoothPingTime.s2xx); + + + if (!peer->hasRealLatency) { + peer->totalEstLatency = peer->smoothPingTime.xbar + 2*peer->buffertimeMs + (1e3*currSamplesPerBlock/getSampleRate()); + } + } + break; + } + case AOO_INVITE_EVENT: + { + aoo_sink_event *e = (aoo_sink_event *)event; + + // accepts invites + if (true){ + EndpointState * es = (EndpointState *) findOrAddRawEndpoint(e->ep.address, e->ep.addrlen); + // handle dummy source specially + + int32_t dummyid; + + if (mAooDummySource->get_id(dummyid) == AOO_OK && dummyid == sourceId) { + // dummy source is special, and creates a remote peer + + RemotePeer * peer = findRemotePeerByRemoteSinkId(es, e->ep.id); + if (peer) { + // we already have a peer for this, interesting + DBG("Already had remote peer for " << es->ipaddr << ":" << es->port << " ourId: " << peer->ourId); + } else if (!mIsConnectedToServer) { + peer = doAddRemotePeerIfNecessary(es); + } + else { + // connected to server, don't just respond to anyone + peer = findRemotePeer(es, -1); + if (!peer) { + DBG("Not reacting to invite from a peer not known in the group"); + break; + } + + DBG("Got invite from peer in the group"); + } + + const ScopedReadLock sl (mCoreLock); + + peer->remoteSinkId = e->ep.id; + + // add their sink + peer->oursource->add_sink(e->ep, peer->aooPeerInfo.flags); + //peer->oursource->set_sinkoption(es, peer->remoteSinkId, aoo_opt_protocol_flags, &e->flags, sizeof(int32_t)); + + if (peer->sendAllow) { + peer->oursource->start(); + peer->sendActive = true; + } else { + peer->oursource->stop(); + peer->sendActive = false; + } + + DBG("Was invited by remote peer " << es->ipaddr << ":" << es->port << " sourceId: " << peer->remoteSinkId << " ourId: " << peer->ourId); + + // now try to invite them back at their port , with the same ID, they + // should have a source waiting for us with the same id + + DBG("Inviting them back to our sink"); + peer->remoteSourceId = peer->remoteSinkId; + peer->oursink->invite_source(e->ep, peer->aooPeerInfo.flags); + + // now remove dummy handshake one + aoo_endpoint dummyep = {es->address.address_ptr(), (int32_t)es->address.length(), dummyid }; + mAooDummySource->remove_sink(dummyep); + + } + else { + // invited + DBG("Invite received to our source: " << sourceId << " from " << es->ipaddr << ":" << es->port << " " << e->ep.id); + + RemotePeer * peer = findRemotePeer(es, sourceId); + if (peer) { + + peer->remoteSinkId = e->ep.id; + + peer->oursource->add_sink(e->ep, peer->aooPeerInfo.flags); + //peer->oursource->set_sinkoption(es, peer->remoteSinkId, aoo_opt_protocol_flags, &e->flags, sizeof(int32_t)); + + if (peer->sendAllow) { + peer->oursource->start(); + + peer->sendActive = true; + DBG("Starting to send, we allow it"); + } else { + peer->sendActive = false; + peer->oursource->stop(); + } + + peer->connected = true; - const ScopedReadLock sl (mCoreLock); - for (int i=0; i < mRemotePeers.size(); ++i) { - auto * peer = mRemotePeers.getUnchecked(i); - if (topeer && topeer != peer) continue; - if (index >= 0 && index != i) continue; + updateRemotePeerUserFormat(-1, peer); + sendRemotePeerInfoUpdate(-1, peer); - osc::OutboundPacketStream msg(buf, sizeof(buf)); + DBG("Finishing peer connection for " << es->ipaddr << ":" << es->port << " " << peer->remoteSinkId); - auto buftimeMs = jmax((double)peer->buffertimeMs, 1e3 * currSamplesPerBlock / getSampleRate()); - info->setProperty("jitbuf", buftimeMs); + } + else { + // find by echo id + if (auto * echopeer = findRemotePeerByEchoId(es, sourceId)) { + echopeer->echosource->add_sink(e->ep, echopeer->aooPeerInfo.flags); + echopeer->echosource->start(); + DBG("Invite to echo source adding sink " << e->ep.id); + } + else if (auto * latpeer = findRemotePeerByLatencyId(es, sourceId)) { + echopeer->latencysource->add_sink(e->ep, latpeer->aooPeerInfo.flags); + echopeer->latencysource->start(); + DBG("Invite to our latency source adding sink " << e->ep.id); + } + else { + // not one of our sources + DBG("No source " << sourceId << " invited, how is this possible?"); + } - String jsonstr = JSON::toString(info.get(), true, 6); + } + } - if (jsonstr.getNumBytesAsUTF8() > AOO_MAXPACKETSIZE - 100) { - DBG("Info too big for packet!"); - return; - } + } else { + DBG("Invite received"); + } - try { - msg << osc::BeginMessage(SONOBUS_FULLMSG_PEERINFO) - << osc::Blob(jsonstr.toRawUTF8(), (int) jsonstr.getNumBytesAsUTF8()) - << osc::EndMessage; - } - catch (const osc::Exception& e){ - DBG("exception in PEERINFO message constructions: " << e.what()); - continue; + break; } + case AOO_UNINVITE_EVENT: + { + aoo_sink_event *e = (aoo_sink_event *)event; - DBG("Sending peerinfo message to " << i); - this->sendPeerMessage(peer, msg.Data(), (int32_t) msg.Size()); - - if (index == i || topeer == peer) break; - } + // accepts uninvites + if (true){ + EndpointState * es = (EndpointState *) findOrAddRawEndpoint(e->ep.address, e->ep.addrlen); + int32_t dummyid; -} + RemotePeer * peer = findRemotePeerByRemoteSinkId(es, e->ep.id); -int32_t SonobusAudioProcessor::sendPeerMessage(RemotePeer * peer, const char *msg, int32_t n) -{ - return endpoint_send(peer->endpoint, msg, n); -} + if (peer) { + int ourid = AOO_ID_NONE; + { + const ScopedReadLock sl (mCoreLock); + ourid = peer->ourId; + peer->oursource->remove_all(); + //peer->oursink->uninvite_all(); // ?? + //peer->connected = false; + peer->sendActive = false; + peer->dataPacketsSent = 0; + if (!peer->recvActive) { + peer->connected = false; + } + } -void SonobusAudioProcessor::doSendData() -{ - // just try to send for everybody - const ScopedReadLock sl (mCoreLock); + //doRemoveRemotePeerIfNecessary(es, ourid); - // send stuff until there is nothing left to send - - int32_t didsomething = 1; + DBG("Uninvited, removed remote peer " << es->ipaddr << ":" << es->port); - auto nowtimems = Time::getMillisecondCounterHiRes(); + } + else if (auto * echopeer = findRemotePeerByEchoId(es, sourceId)) { + echopeer->echosource->remove_sink(e->ep); + echopeer->echosource->stop(); + DBG("UnInvite to echo source adding sink " << e->ep.id); + } + else if (auto * latpeer = findRemotePeerByLatencyId(es, sourceId)) { + echopeer->latencysource->remove_sink(e->ep); + echopeer->latencysource->stop(); + DBG("UnInvite to latency source adding sink " << e->ep.id); + } - while (didsomething) { - //mAooSource->send(); - didsomething = 0; - - didsomething |= mAooDummySource->send(); - - if (mAooClient) { - mAooClient->send(); - } - - for (auto & remote : mRemotePeers) { - if (remote->oursource) { - didsomething |= remote->oursource->send(); - - if (didsomething) { - remote->dataPacketsSent += 1; + else { + DBG("Uninvite received to unknown"); } - } - if (remote->oursink) { - didsomething |= remote->oursink->send(); - } - if (remote->latencysource) { - didsomething |= remote->latencysource->send(); - didsomething |= remote->latencysink->send(); - didsomething |= remote->echosource->send(); - didsomething |= remote->echosink->send(); + } else { + DBG("Uninvite received"); } - if ( nowtimems > (remote->lastSendPingTimeMs + PEER_PING_INTERVAL_MS) ) { - sendPingEvent(remote); - remote->lastSendPingTimeMs = nowtimems; - if (!remote->haveSentFirstPeerInfo) { - sendRemotePeerInfoUpdate(-1, remote); - remote->haveSentFirstPeerInfo = true; - } - } + break; } - } +#if 0 + case AOO_CHANGECODEC_EVENT: + { + aoo_source_event *e = (aoo_source_event *)event; + DBG("Change codec received from sink " << e->id); - if (mPendingUnmute.get() && mPendingUnmuteAtStamp < Time::getMillisecondCounter() ) { - DBG("UNMUTING ALL"); - mState.getParameter(paramMainRecvMute)->setValueNotifyingHost(0.0f); + EndpointState * es = (EndpointState *) findOrAddRawEndpoint(e->ep.address, e->ep.addrlen); - mPendingUnmute = false; - } + RemotePeer * peer = findRemotePeerByRemoteSinkId(es, e->id); - if (mNeedsSampleSetup.get()) { - DBG("Doing sample setup for all"); - setupSourceFormatsForAll(); - mNeedsSampleSetup = false; + if (peer) { + // now we need to set our latency and echo source to match our main source's format + aoo_format_storage fmt; + if (peer->oursource->get_format(fmt) > 0) { + peer->latencysource->set_format(fmt.header); + peer->echosource->set_format(fmt.header); - // reset all incoming by toggling muting - if (!mMainRecvMute.get()) { - DBG("Toggling main recv mute"); - mState.getParameter(paramMainRecvMute)->setValueNotifyingHost(1.0f); - mPendingUnmuteAtStamp = Time::getMillisecondCounter() + 250; - mPendingUnmute = true; - } + AudioCodecFormatCodec codec = String(fmt.header.codec) == AOO_CODEC_OPUS ? CodecOpus : CodecPCM; + if (codec == CodecOpus) { + aoo_format_opus *ofmt = (aoo_format_opus *)&fmt; + int retindex = findFormatIndex(codec, ofmt->bitrate / ofmt->header.nchannels, 0); + if (retindex >= 0) { + peer->formatIndex = retindex; // new sending format index + } + } + else if (codec == CodecPCM) { + aoo_format_pcm *pfmt = (aoo_format_pcm *)&fmt; + int bdepth = pfmt->bitdepth == AOO_PCM_FLOAT32 ? 4 : pfmt->bitdepth == AOO_PCM_INT24 ? 3 : pfmt->bitdepth == AOO_PCM_FLOAT64 ? 8 : 2; + int retindex = findFormatIndex(codec, 0, bdepth); + if (retindex >= 0) { + peer->formatIndex = retindex; // new sending format index + } + } + } - sendRemotePeerInfoUpdate(-1); // send to all + } + } +#endif + default: + break; } - + return AOO_OK; } -struct ProcessorIdPair -{ - ProcessorIdPair(SonobusAudioProcessor *proc, int32_t id_) : processor(proc), id(id_) {} - SonobusAudioProcessor * processor; - int32_t id; -}; -static int32_t gHandleSourceEvents(void * user, const aoo_event ** events, int32_t n) +void SonobusAudioProcessor::handleEvents() { - ProcessorIdPair * pp = static_cast (user); - return pp->processor->handleSourceEvents(events, n, pp->id); -} + const ScopedReadLock sl (mCoreLock); + int32_t dummy = 0; -static int32_t gHandleSinkEvents(void * user, const aoo_event ** events, int32_t n) -{ - ProcessorIdPair * pp = static_cast (user); - return pp->processor->handleSinkEvents(events, n, pp->id); -} + if (mAooDummySource->events_available() > 0) { + mAooDummySource->poll_events(); + } -static int32_t gHandleServerEvents(void * user, const aoo_event ** events, int32_t n) -{ - ProcessorIdPair * pp = static_cast (user); - return pp->processor->handleServerEvents(events, n); -} + for (auto & remote : mRemotePeers) { + if (remote->oursource) { + remote->oursource->poll_events(); + } + if (remote->oursink) { + remote->oursink->poll_events(); + } -static int32_t gHandleClientEvents(void * user, const aoo_event ** events, int32_t n) -{ - ProcessorIdPair * pp = static_cast (user); - return pp->processor->handleClientEvents(events, n); -} + if (remote->latencysink) { + remote->latencysink->poll_events(); + } + if (remote->echosink) { + remote->echosink->poll_events(); + } -void SonobusAudioProcessor::handleEvents() -{ - const ScopedReadLock sl (mCoreLock); - int32_t dummy = 0; - + if (remote->latencysource) { + remote->latencysource->poll_events(); + } + if (remote->echosource) { + remote->echosource->poll_events(); + } + + } + + +#if 0 if (mAooServer /*&& mAooServer->events_available()*/) { ProcessorIdPair pp(this, dummy); mAooServer->handle_events(gHandleServerEvents, &pp); @@ -3072,13 +4338,13 @@ void SonobusAudioProcessor::handleEvents() } } - +#endif } void SonobusAudioProcessor::sendPingEvent(RemotePeer * peer) { - auto tt = aoo_osctime_get(); + auto tt = aoo_osctime_now(); char buf[AOO_MAXPACKETSIZE]; osc::OutboundPacketStream outmsg(buf, sizeof(buf)); @@ -3127,7 +4393,7 @@ void SonobusAudioProcessor::handlePingEvent(EndpointState * endpoint, uint64_t t peer->gotNewStylePing = true; } - +#if 0 int32_t SonobusAudioProcessor::handleSourceEvents(const aoo_event ** events, int32_t n, int32_t sourceId) { for (int i = 0; i < n; ++i){ @@ -3172,7 +4438,7 @@ int32_t SonobusAudioProcessor::handleSourceEvents(const aoo_event ** events, int int32_t dummyid; - if (mAooDummySource->get_id(dummyid) && dummyid == sourceId) { + if (mAooDummySource->get_id(dummyid) == AOO_OK && dummyid == sourceId) { // dummy source is special, and creates a remote peer RemotePeer * peer = findRemotePeerByRemoteSinkId(es, e->id); @@ -3390,7 +4656,7 @@ int32_t SonobusAudioProcessor::handleSinkEvents(const aoo_event ** events, int32 // someone has added us, thus accepting our invitation int32_t dummyid; - if (mAooDummySource->get_id(dummyid) && dummyid == e->id ) { + if (mAooDummySource->get_id(dummyid) == AOO_OK && dummyid == e->id ) { // ignoring dummy add DBG("Got dummy handshake add from " << es->ipaddr << ":" << es->port); } @@ -3437,7 +4703,7 @@ int32_t SonobusAudioProcessor::handleSinkEvents(const aoo_event ** events, int32 RemotePeer * peer = findRemotePeer(es, sinkId); if (peer) { aoo_format_storage f; - if (peer->oursink->get_source_format(e->endpoint, e->id, f) > 0) { + if (peer->oursink->get_source_format(e->endpoint, e->id, f) == AOO_OK) { DBG("Got source format event from " << es->ipaddr << ":" << es->port << " " << e->id << " channels: " << f.header.nchannels); peer->recvMeterSource.resize(f.header.nchannels, meterRmsWindow); @@ -3971,7 +5237,7 @@ int32_t SonobusAudioProcessor::handleClientEvents(const aoo_event ** events, int DBG("Peer joined group " << e->group << " - user " << e->user); if (mAutoconnectGroupPeers) { - connectRemotePeerRaw(e->address, CharPointer_UTF8 (e->user), CharPointer_UTF8 (e->group), !mMainRecvMute.get()); + connectRemotePeerRaw(e->address, e->addrlen, CharPointer_UTF8 (e->user), CharPointer_UTF8 (e->group), !mMainRecvMute.get()); } //aoo_node_add_peer(x->x_node, gensym(e->group), gensym(e->user), @@ -4009,7 +5275,7 @@ int32_t SonobusAudioProcessor::handleClientEvents(const aoo_event ** events, int DBG("Peer leave group " << e->group << " - user " << e->user); - EndpointState * endpoint = findOrAddRawEndpoint(e->address); + EndpointState * endpoint = findOrAddRawEndpoint(e->address, e->addrlen); if (endpoint) { removeAllRemotePeersWithEndpoint(endpoint); @@ -4037,11 +5303,11 @@ int32_t SonobusAudioProcessor::handleClientEvents(const aoo_event ** events, int } return 1; } +#endif - -int SonobusAudioProcessor::connectRemotePeerRaw(void * sockaddr, const String & username, const String & groupname, bool reciprocate) +int SonobusAudioProcessor::connectRemotePeerRaw(const void * sockaddr, int addrlen, const String & username, const String & groupname, bool reciprocate) { - EndpointState * endpoint = findOrAddRawEndpoint(sockaddr); + EndpointState * endpoint = findOrAddRawEndpoint(sockaddr, addrlen); if (!endpoint) { DBG("Error getting endpoint from raw address"); @@ -4052,11 +5318,13 @@ int SonobusAudioProcessor::connectRemotePeerRaw(void * sockaddr, const String & remote->recvAllow = !mMainRecvMute.get(); - // special - use 0 - bool ret = remote->oursink->invite_source(endpoint, 0, endpoint_send) == 1; + // special - use 0 + aoo_endpoint aep = { endpoint->address.address_ptr(), (int32_t) endpoint->address.length(), 0 }; + + bool ret = remote->oursink->invite_source(aep, remote->aooPeerInfo.flags) == AOO_OK; if (ret) { - DBG("Successfully invited remote peer at " << endpoint->ipaddr << ":" << endpoint->port << " - ourId " << remote->ourId); + DBG("Successfully invited remote peer at " << endpoint->ipaddr << ":" << endpoint->port << " - ourId " << remote->ourId << " flags: " << (int)remote->aooPeerInfo.flags); remote->connected = true; remote->invitedPeer = reciprocate; //remote->recvActive = reciprocate; @@ -4086,8 +5354,9 @@ int SonobusAudioProcessor::connectRemotePeer(const String & host, int port, cons remote->recvAllow = !mMainRecvMute.get(); - // special - use 0 - bool ret = remote->oursink->invite_source(endpoint, 0, endpoint_send) == 1; + // special - use 0 + aoo_endpoint aep = { endpoint->address.address_ptr(), (int32_t) endpoint->address.length(), 0 }; + bool ret = remote->oursink->invite_source(aep, remote->aooPeerInfo.flags) == AOO_OK; if (ret) { DBG("Successfully invited remote peer at " << host << ":" << port << " - ourId " << remote->ourId); @@ -4274,6 +5543,18 @@ bool SonobusAudioProcessor::removeAllRemotePeers() { const ScopedWriteLock slw (mCoreLock); mRemotePeers.clearQuick(false); // not deleting objects here + + for (int index = 0; index < removed.size(); ++index) { + auto remote = removed.getUnchecked(index); + + mAooClient->remove_sink(remote->oursink.get()); + mAooClient->remove_sink(remote->latencysink.get()); + mAooClient->remove_sink(remote->echosink.get()); + + mAooClient->remove_source(remote->oursource.get()); + mAooClient->remove_source(remote->latencysource.get()); + mAooClient->remove_source(remote->echosource.get()); + } } // reset matrix @@ -4311,6 +5592,14 @@ bool SonobusAudioProcessor::removeRemotePeer(int index) { const ScopedWriteLock slw (mCoreLock); + mAooClient->remove_sink(remote->oursink.get()); + mAooClient->remove_sink(remote->latencysink.get()); + mAooClient->remove_sink(remote->echosink.get()); + + mAooClient->remove_source(remote->oursource.get()); + mAooClient->remove_source(remote->latencysource.get()); + mAooClient->remove_source(remote->echosource.get()); + mRemotePeers.remove(index, false); // not deleting in scoped write lock } @@ -4992,10 +6281,12 @@ void SonobusAudioProcessor::setRemotePeerRecvActive(int index, bool active) #if 1 if (active) { DBG("inviting peer " << remote->ourId << " source " << remote->remoteSourceId); - remote->oursink->invite_source(remote->endpoint,remote->remoteSourceId, endpoint_send); + aoo_endpoint aep = { remote->endpoint->address.address_ptr(), (int32_t) remote->endpoint->address.length(), remote->remoteSourceId }; + remote->oursink->invite_source(aep, remote->aooPeerInfo.flags); } else { DBG("uninviting peer " << remote->ourId << " source " << remote->remoteSourceId); - remote->oursink->uninvite_source(remote->endpoint, remote->remoteSourceId, endpoint_send); + aoo_endpoint aep = { remote->endpoint->address.address_ptr(), (int32_t) remote->endpoint->address.length(), remote->remoteSourceId }; + remote->oursink->uninvite_source(aep); } #endif } @@ -5359,10 +6650,12 @@ bool SonobusAudioProcessor::startRemotePeerLatencyTest(int index, float duration remote->latencysink->reset(); remote->latencysource->remove_all(); - remote->latencysink->invite_source(remote->endpoint, remote->remoteSourceId+ECHO_ID_OFFSET, endpoint_send); + aoo_endpoint aep = { remote->endpoint->address.address_ptr(), (int32_t) remote->endpoint->address.length(), remote->remoteSourceId+ECHO_ID_OFFSET }; + remote->latencysink->invite_source(aep, remote->aooPeerInfo.flags); // start our latency source sending to remote's echosink - remote->latencysource->add_sink(remote->endpoint, remote->remoteSinkId+ECHO_ID_OFFSET, endpoint_send); + aoo_endpoint asep = { remote->endpoint->address.address_ptr(), (int32_t) remote->endpoint->address.length(), remote->remoteSinkId+ECHO_ID_OFFSET }; + remote->latencysource->add_sink(asep, remote->aooPeerInfo.flags); remote->latencysource->start(); #if 1 @@ -5606,11 +6899,60 @@ SonobusAudioProcessor::RemotePeer * SonobusAudioProcessor::doAddRemotePeerIfNece retpeer->resetSafetyMuted = retpeer->buffertimeMs < 3.0f; + retpeer->oursinkpp.processor = this; + retpeer->oursink->get_id(retpeer->oursinkpp.id); + retpeer->oursink->set_eventhandler( + [](void *user, const aoo_event *event, int32_t level){ + auto * pp = static_cast(user); + pp->processor->handleAooSinkEvent(event, level, pp->id); + }, &retpeer->oursinkpp, AOO_EVENT_POLL); + + retpeer->latsinkpp.processor = this; + retpeer->latencysink->get_id(retpeer->latsinkpp.id); + retpeer->latencysink->set_eventhandler( + [](void *user, const aoo_event *event, int32_t level){ + auto * pp = static_cast(user); + pp->processor->handleAooSinkEvent(event, level, pp->id); + }, &retpeer->latsinkpp, AOO_EVENT_POLL); + + retpeer->echosinkpp.processor = this; + retpeer->echosink->get_id(retpeer->echosinkpp.id); + retpeer->echosink->set_eventhandler( + [](void *user, const aoo_event *event, int32_t level){ + auto * pp = static_cast(user); + pp->processor->handleAooSinkEvent(event, level, pp->id); + }, &retpeer->echosinkpp, AOO_EVENT_POLL); + + + retpeer->oursourcepp.processor = this; + retpeer->oursource->get_id(retpeer->oursourcepp.id); + retpeer->oursource->set_eventhandler( + [](void *user, const aoo_event *event, int32_t level){ + auto * pp = static_cast(user); + pp->processor->handleAooSourceEvent(event, level, pp->id); + }, &retpeer->oursourcepp, AOO_EVENT_POLL); + + retpeer->latsourcepp.processor = this; + retpeer->latencysource->get_id(retpeer->latsourcepp.id); + retpeer->latencysource->set_eventhandler( + [](void *user, const aoo_event *event, int32_t level){ + auto * pp = static_cast(user); + pp->processor->handleAooSourceEvent(event, level, pp->id); + }, &retpeer->latsourcepp, AOO_EVENT_POLL); + + retpeer->echosourcepp.processor = this; + retpeer->echosource->get_id(retpeer->echosourcepp.id); + retpeer->echosource->set_eventhandler( + [](void *user, const aoo_event *event, int32_t level){ + auto * pp = static_cast(user); + pp->processor->handleAooSourceEvent(event, level, pp->id); + }, &retpeer->echosourcepp, AOO_EVENT_POLL); + retpeer->oursink->setup(getSampleRate(), currSamplesPerBlock, getMainBusNumOutputChannels()); retpeer->oursink->set_buffersize(retpeer->buffertimeMs); - int32_t flags = AOO_PROTOCOL_FLAG_COMPACT_DATA; - retpeer->oursink->set_option(aoo_opt_protocol_flags, &flags, sizeof(int32_t)); + //int32_t flags = AOO_PROTOCOL_FLAG_COMPACT_DATA; + //retpeer->oursink->set_option(aoo_opt_protocol_flags, &flags, sizeof(int32_t)); retpeer->nominalSendChannels = mSendChannels.get(); retpeer->sendChannels = mSendChannels.get() <= 0 ? mActiveSendChannels : mSendChannels.get(); @@ -5633,8 +6975,8 @@ SonobusAudioProcessor::RemotePeer * SonobusAudioProcessor::doAddRemotePeerIfNece retpeer->latencysink->setup(getSampleRate(), currSamplesPerBlock, 1); retpeer->echosink->setup(getSampleRate(), currSamplesPerBlock, 1); - retpeer->latencysink->set_option(aoo_opt_protocol_flags, &flags, sizeof(int32_t)); - retpeer->echosink->set_option(aoo_opt_protocol_flags, &flags, sizeof(int32_t)); + //retpeer->latencysink->set_option(aoo_opt_protocol_flags, &flags, sizeof(int32_t)); + //retpeer->echosink->set_option(aoo_opt_protocol_flags, &flags, sizeof(int32_t)); retpeer->latencysink->set_buffersize(retpeer->buffertimeMs); retpeer->echosink->set_buffersize(retpeer->buffertimeMs); @@ -5650,9 +6992,9 @@ SonobusAudioProcessor::RemotePeer * SonobusAudioProcessor::doAddRemotePeerIfNece retpeer->latencysource->set_ping_interval(2000); retpeer->echosource->set_ping_interval(2000); - retpeer->oursource->set_respect_codec_change_requests(1); - retpeer->latencysource->set_respect_codec_change_requests(1); - retpeer->echosource->set_respect_codec_change_requests(1); + //retpeer->oursource->set_respect_codec_change_requests(1); + //retpeer->latencysource->set_respect_codec_change_requests(1); + //retpeer->echosource->set_respect_codec_change_requests(1); //retpeer->latencyProcessor.reset(new MTDM(getSampleRate())); retpeer->latencyMeasurer.reset(new LatencyMeasurer()); @@ -5682,10 +7024,25 @@ SonobusAudioProcessor::RemotePeer * SonobusAudioProcessor::doAddRemotePeerIfNece retpeer->chanGroups[chgrpi].init(getSampleRate()); }; + if (mAooClient->get_peer_info(retpeer->endpoint->address.address_ptr(), retpeer->endpoint->address.length(), &(retpeer->aooPeerInfo)) == AOO_OK) { + bool legacy = (retpeer->aooPeerInfo.flags & AOO_ENDPOINT_LEGACY) ? true : false; + retpeer->oursource->set_binary_data_msg(!legacy); + retpeer->latencysource->set_binary_data_msg(!legacy); + retpeer->echosource->set_binary_data_msg(!legacy); + } // now add it, once initialized { const ScopedWriteLock slw (mCoreLock); + + mAooClient->add_sink(retpeer->oursink.get(), retpeer->oursinkpp.id); + mAooClient->add_sink(retpeer->latencysink.get(), retpeer->latsinkpp.id); + mAooClient->add_sink(retpeer->echosink.get(), retpeer->echosinkpp.id); + + mAooClient->add_source(retpeer->oursource.get(), retpeer->oursourcepp.id); + mAooClient->add_source(retpeer->latencysource.get(), retpeer->latsourcepp.id); + mAooClient->add_source(retpeer->echosource.get(), retpeer->echosourcepp.id); + mRemotePeers.add(retpeer); } @@ -5839,6 +7196,15 @@ bool SonobusAudioProcessor::removeAllRemotePeersWithEndpoint(EndpointState * end { const ScopedWriteLock slw (mCoreLock); + + mAooClient->remove_sink(s->oursink.get()); + mAooClient->remove_sink(s->latencysink.get()); + mAooClient->remove_sink(s->echosink.get()); + + mAooClient->remove_source(s->oursource.get()); + mAooClient->remove_source(s->latencysource.get()); + mAooClient->remove_source(s->echosource.get()); + removed.add(mRemotePeers.removeAndReturn(i)); } } @@ -5865,6 +7231,14 @@ bool SonobusAudioProcessor::doRemoveRemotePeerIfNecessary(EndpointState * endpoi { const ScopedWriteLock slw (mCoreLock); + mAooClient->remove_sink(s->oursink.get()); + mAooClient->remove_sink(s->latencysink.get()); + mAooClient->remove_sink(s->echosink.get()); + + mAooClient->remove_source(s->oursource.get()); + mAooClient->remove_source(s->latencysource.get()); + mAooClient->remove_source(s->echosource.get()); + removed.add(mRemotePeers.removeAndReturn(i)); } break; @@ -5902,6 +7276,7 @@ bool SonobusAudioProcessor::formatInfoToAooFormat(const AudioCodecFormatInfo & i fmt->header.blocksize = currSamplesPerBlock >= info.min_preferred_blocksize ? currSamplesPerBlock : info.min_preferred_blocksize; fmt->header.samplerate = getSampleRate(); fmt->header.nchannels = channels; + fmt->header.size = sizeof(aoo_format_pcm); fmt->bitdepth = info.bitdepth == 2 ? AOO_PCM_INT16 : info.bitdepth == 3 ? AOO_PCM_INT24 : info.bitdepth == 4 ? AOO_PCM_FLOAT32 : info.bitdepth == 8 ? AOO_PCM_FLOAT64 : AOO_PCM_INT16; return true; @@ -5912,6 +7287,7 @@ bool SonobusAudioProcessor::formatInfoToAooFormat(const AudioCodecFormatInfo & i fmt->header.blocksize = currSamplesPerBlock >= info.min_preferred_blocksize ? currSamplesPerBlock : info.min_preferred_blocksize; fmt->header.samplerate = getSampleRate(); fmt->header.nchannels = channels; + fmt->header.size = sizeof(aoo_format_opus); fmt->bitrate = info.bitrate * fmt->header.nchannels; fmt->complexity = info.complexity; fmt->signal_type = info.signal_type; @@ -5925,7 +7301,7 @@ bool SonobusAudioProcessor::formatInfoToAooFormat(const AudioCodecFormatInfo & i } -void SonobusAudioProcessor::setupSourceFormat(SonobusAudioProcessor::RemotePeer * peer, aoo::isource * source, bool latencymode) +void SonobusAudioProcessor::setupSourceFormat(SonobusAudioProcessor::RemotePeer * peer, aoo::source * source, bool latencymode) { // have choice and parameters int formatIndex = (!peer || peer->formatIndex < 0) ? mDefaultAudioFormatIndex : peer->formatIndex; @@ -5980,7 +7356,7 @@ ValueTree SonobusAudioProcessor::getSendUserFormatLayoutTree() } -void SonobusAudioProcessor::setupSourceUserFormat(RemotePeer * peer, aoo::isource * source) +void SonobusAudioProcessor::setupSourceUserFormat(RemotePeer * peer, aoo::source * source) { // get userformat from send info ValueTree fmttree = getSendUserFormatLayoutTree(); @@ -5992,7 +7368,8 @@ void SonobusAudioProcessor::setupSourceUserFormat(RemotePeer * peer, aoo::isourc fmttree.writeToStream(stream); - source->set_userformat(destData.getData(), (int32_t) destData.getSize()); + JUCE_COMPILER_WARNING ("TODO: USERFORMAT"); + // source->set_userformat(destData.getData(), (int32_t) destData.getSize()); } void SonobusAudioProcessor::updateRemotePeerUserFormat(int index, RemotePeer * onlypeer) @@ -7004,7 +8381,7 @@ void SonobusAudioProcessor::processBlock (AudioBuffer& buffer, MidiBuffer // interleaved by keeping the same state. - uint64_t t = aoo_osctime_get(); + uint64_t t = aoo_osctime_now(); // meter input pre everything inputMeterSource.measureBlock (buffer, 0, numSamples); @@ -7356,7 +8733,9 @@ void SonobusAudioProcessor::processBlock (AudioBuffer& buffer, MidiBuffer // calculate fill ratio before processing the sink float retratio = 0.0f; - if (remote->oursink->get_sourceoption(remote->endpoint, remote->remoteSourceId, aoo_opt_buffer_fill_ratio, &retratio, sizeof(retratio)) > 0) { + aoo_endpoint aep = { remote->endpoint->address.address_ptr(), (int32_t) remote->endpoint->address.length(), remote->remoteSourceId }; + + if (remote->oursink->get_buffer_fill_ratio(aep, retratio) == AOO_OK) { remote->fillRatio.Z *= 0.95; remote->fillRatio.push(retratio); remote->fillRatioSlow.Z *= 0.99; diff --git a/Source/SonobusPluginProcessor.h b/Source/SonobusPluginProcessor.h index f448ff96..951d64d5 100644 --- a/Source/SonobusPluginProcessor.h +++ b/Source/SonobusPluginProcessor.h @@ -9,6 +9,7 @@ #include "aoo/aoo.hpp" #include "aoo/aoo_net.hpp" +#include "common/net_utils.hpp" #include #include @@ -266,10 +267,17 @@ class SonobusAudioProcessor : public AudioProcessor, public AudioProcessorValue struct RemoteSource; struct RemotePeer; + int32_t handleAooServerEvent(const aoo_event *event, int32_t level); + int32_t handleAooClientEvent(const aoo_event *event, int32_t level); + int32_t handleAooSinkEvent(const aoo_event *event, int32_t level, int32_t sinkId); + int32_t handleAooSourceEvent(const aoo_event *event, int32_t level, int32_t sourceId); + +#if 0 int32_t handleSourceEvents(const aoo_event ** events, int32_t n, int32_t sourceId); int32_t handleSinkEvents(const aoo_event ** events, int32_t n, int32_t sinkId); int32_t handleServerEvents(const aoo_event ** events, int32_t n); int32_t handleClientEvents(const aoo_event ** events, int32_t n); +#endif // server stuff void startAooServer(); @@ -306,9 +314,12 @@ class SonobusAudioProcessor : public AudioProcessor, public AudioProcessorValue String getCurrentUsername() const { return mCurrentUsername; } // peer stuff - + + EndpointState * findEndpoint(const aoo::ip_address & ipaddr); + + EndpointState * findOrAddEndpoint(const aoo::ip_address & ipaddr); EndpointState * findOrAddEndpoint(const String & host, int port); - EndpointState * findOrAddRawEndpoint(void * rawaddr); + EndpointState * findOrAddRawEndpoint(const void * rawaddr, int addrlen); int getUdpLocalPort() const { return mUdpLocalPort; } IPAddress getLocalIPAddress() const { return mLocalIPAddress; } @@ -740,6 +751,9 @@ class SonobusAudioProcessor : public AudioProcessor, public AudioProcessorValue void setLanguageOverrideCode(const String & code) { mLangOverrideCode = code; } String getLanguageOverrideCode() const { return mLangOverrideCode; } + static int32_t udpsend(void *user, const char *msg, int32_t size, + const void *addr, int32_t addrlen, uint32_t flags); + private: //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SonobusAudioProcessor) @@ -790,10 +804,10 @@ class SonobusAudioProcessor : public AudioProcessor, public AudioProcessorValue void updateSafetyMuting(RemotePeer * peer); - void setupSourceFormat(RemotePeer * peer, aoo::isource * source, bool latencymode=false); + void setupSourceFormat(RemotePeer * peer, aoo::source * source, bool latencymode=false); bool formatInfoToAooFormat(const AudioCodecFormatInfo & info, int channels, aoo_format_storage & retformat); - void setupSourceUserFormat(RemotePeer * peer, aoo::isource * source); + void setupSourceUserFormat(RemotePeer * peer, aoo::source * source); RemotePeer * findRemotePeer(EndpointState * endpoint, int32_t ourId); @@ -829,7 +843,7 @@ class SonobusAudioProcessor : public AudioProcessor, public AudioProcessorValue void restoreLayoutFormatForPeer(RemotePeer * remote, bool resetmulti=false); - int connectRemotePeerRaw(void * sockaddr, const String & username = "", const String & groupname = "", bool reciprocate=true); + int connectRemotePeerRaw(const void * sockaddr, int addrlen, const String & username = "", const String & groupname = "", bool reciprocate=true); int findFormatIndex(AudioCodecFormatCodec codec, int bitrate, int bitdepth); @@ -976,10 +990,10 @@ class SonobusAudioProcessor : public AudioProcessor, public AudioProcessorValue foleys::LevelMeterSource metMeterSource; // AOO stuff - aoo::isource::pointer mAooDummySource; + aoo::source::pointer mAooDummySource; - aoo::net::iserver::pointer mAooServer; - aoo::net::iclient::pointer mAooClient; + aoo::net::server::pointer mAooServer; + aoo::net::client::pointer mAooClient; std::unique_ptr mServerEndpoint; @@ -997,10 +1011,12 @@ class SonobusAudioProcessor : public AudioProcessor, public AudioProcessorValue // we will add sinks for any peer we invite, as part of a RemoteSource - std::unique_ptr mUdpSocket; + //std::unique_ptr mUdpSocket; + int mUdpSocketHandle = -1; int mUdpLocalPort; IPAddress mLocalIPAddress; - + aoo::ip_address mLocalClientAddress; + class SendThread; class RecvThread; class EventThread; diff --git a/deps/aoo/.git-ci/gitlab-iem.yml b/deps/aoo/.git-ci/gitlab-iem.yml index aba8fd1a..2c43bc1e 100644 --- a/deps/aoo/.git-ci/gitlab-iem.yml +++ b/deps/aoo/.git-ci/gitlab-iem.yml @@ -1,6 +1,266 @@ ---- +### global variables + variables: - SRCDIR: pd/ + PDVERSION: 0.51-1 + VST2DIR: /tmp/vst2sdk + VST3DIR: /tmp/vst3sdk + SC_PATH: /tmp/supercollider + +### script snippets + +.script:deps: &script_deps +# supercollider + - git clone https://github.com/supercollider/supercollider.git "${SC_PATH}" + - pushd "${SC_PATH}" + - git submodule update --init + - popd +# submodules (Opus) + - git submodule update --init + - cmake -Hdeps/opus -Bdeps/opus/build -DOPUS_STACK_PROTECTOR=OFF + -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE -DCMAKE_INSTALL_PREFIX=deps/opus + - make -C deps/opus/build install VERBOSE=1 + +.script:cmake: &script_cmake + - prefix="$(pwd)/build/${CI_JOB_NAME%_*}" + - cmake -H. -Bbuild -DSTATIC_LIBS=${STATIC_LIBS} -DCMAKE_INSTALL_PREFIX="$prefix/aoo" + -DPD_EXTENSION=${PD_EXTENSION} -DPD_DIR="${PD_PATH}" -DPD_INSTALLDIR="$prefix/pd" + -DSUPERNOVA=${SUPERNOVA} -DSC_INCLUDEDIR="${SC_PATH}" -DSC_INSTALLDIR="$prefix/sc" + +### build snippets +.script:make: &script_make + - make -C build VERBOSE=1 +.script:make_install: &script_make_install + - make -C build install/strip VERBOSE=1 + +####################################################################### +### configuration templates (to be used for snapshot and release builds) +.build:script: + stage: build + script: + - *script_make + - *script_make_install + artifacts: + name: ${CI_PROJECT_NAME}_${CI_COMMIT_REF_NAME}_${CI_JOB_NAME%_*} + paths: + - "build/${CI_JOB_NAME%_*}/aoo" + - "build/${CI_JOB_NAME%_*}/pd/aoo" + - "build/${CI_JOB_NAME%_*}/sc/aoo" + +.snapshot: + except: + - tags + artifacts: + expire_in: 1 week + +.release: + only: + - tags + +.build:linux: &build_linux + extends: .build:script + image: gcc + variables: + SUPERNOVA: 1 + STATIC_LIBS: 1 + before_script: + - apt-get update && apt-get install -y --no-install-recommends make cmake git + puredata-dev puredata + - *script_deps + - *script_cmake + +.build:linux_i386: &build_linux_i386 + extends: .build:linux + image: registry.git.iem.at/devtools/docker/debiancross:i386 + variables: + PD_EXTENSION: l_i386 + +.build:linux_armhf: &build_linux_armhf + extends: .build:linux + image: registry.git.iem.at/devtools/docker/debiancross:armhf + variables: + PD_EXTENSION: l_arm + +.build:linux_arm64: &build_linux_arm64 + extends: .build:linux + image: registry.git.iem.at/devtools/docker/debiancross:arm64 + variables: + PD_EXTENSION: l_arm64 + +.build:macos: &build_macos + extends: .build:script + tags: + - osx + variables: + SUPERNOVA: 1 + before_script: + - wget -q -O Pd.tgz http://msp.ucsd.edu/Software/pd-${PDVERSION}.mac.tar.gz + - rm -rf /Applications/Pd*.app/ + - tar xvf Pd.tgz -C /Applications/ + - rm -f Pd.tgz + - *script_deps + - *script_cmake + +.build:w32: &build_w32 + extends: .build:script + tags: + - windows + variables: + IEMCI_CONFIGURATIONS: mingw32 + SUPERNOVA: 1 + STATIC_LIBS: 1 + PD_URL: http://msp.ucsd.edu/Software/pd-${PDVERSION}-i386.msw.zip + before_script: + - pacman --noconfirm -S cmake + - wget -q -O Pd.zip ${PD_URL} +# install locally to avoid hassles with Windows vs. Unix file paths. +# NOTE: PD_PATH is also used in .script:cmake. + - export PD_PATH=/tmp/pd + - rm -rf "${PD_PATH}"; mkdir -p "${PD_PATH}" + - unzip -q Pd.zip -d "${PD_PATH}" + - mv -v "${PD_PATH}"/*/* "${PD_PATH}" + - *script_deps + - *script_cmake + +.build:w64: &build_w64 + extends: .build:w32 + variables: + IEMCI_CONFIGURATIONS: mingw64 + PD_EXTENSION: m_amd64 + PD_URL: http://msp.ucsd.edu/Software/pd-${PDVERSION}.msw.zip + +### job templates +.Linux: + <<: *build_linux +.Linux_i386: + allow_failure: false + <<: *build_linux_i386 +.Linux_ARMhf: + allow_failure: false + <<: *build_linux_armhf +.Linux_ARM64: + allow_failure: false + <<: *build_linux_arm64 +.Darwin: + <<: *build_macos +.w32: + <<: *build_w32 +.w64: + <<: *build_w64 + +####################################################################### +### create deken packages and (optionally) upload them; +### if you want to automatically upload a package, you need to +### set DEKEN_USERNAME/DEKEN_PASSWORD in the CI-project settings. +### (https://git.iem.at/help/ci/variables/README#variables) +.package: + stage: deploy + image: debian:buster + variables: + DEKEN_ROOT: "yes" + before_script: + - apt-get update && apt-get --no-install-recommends -y install deken zip + script: +# create zip files for all platform (e.g. aoo_v0.3.0_w32.zip) + - rm -f ./*.zip + - root=$(pwd) +# Pd + SuperCollider: + - for dir in ./build/*; do + for lib in pd sc; do + name=aoo_${lib}_${CI_COMMIT_REF_NAME}_$(basename "$dir").zip; + echo create $name; + (cd "$dir/$lib" && zip -r "${root}/${name}" ./aoo) + ; done + ; done +# C/C++ library: + - for dir in ./build/*; do + name=aoo_${CI_COMMIT_REF_NAME}_$(basename "$dir").zip; + echo create $name; + (cd "$dir" && zip -r "${root}/${name}" ./aoo) + ; done +# Deken: + - rm -f ./*.dek + - rm -rf ./pd/aoo + - mkdir -p ./pd/aoo +# create a single deken package containing binaries for all platforms + - for dir in ./build/*/pd/aoo; do cp -r $dir/* ./pd/aoo; done + - deken package --version="${CI_COMMIT_TAG#v}" ./pd/aoo +# upload deken package (optional) + - test -z "${CI_COMMIT_TAG}" || test -z "${DEKEN_USERNAME}" || test -z "${DEKEN_PASSWORD}" || deken upload --no-source-error ./*.dek + artifacts: + name: ${CI_PROJECT_NAME}_${CI_COMMIT_REF_NAME}_package + paths: + - ./*.dek + - ./*.zip + + +####################################################################### +### the actual jobs: (linux,macos,windows)*(release,snapshot) + +### release jobs +Linux: + extends: + - .Linux + - .release +Linux_i386: + extends: + - .Linux_i386 + - .release +Linux_ARMhf: + extends: + - .Linux_ARMhf + - .release +Linux_ARM64: + extends: + - .Linux_ARM64 + - .release +Darwin: + extends: + - .Darwin + - .release +w32: + extends: + - .w32 + - .release +w64: + extends: + - .w64 + - .release +package: + extends: + - .package + - .release + +### snapshot jobs +Linux_snapshot: + extends: + - .Linux + - .snapshot +Linux_i386_snapshot: + extends: + - .Linux_i386 + - .snapshot +Linux_ARMhf_snapshot: + extends: + - .Linux_ARMhf + - .snapshot +Linux_ARM64_snapshot: + extends: + - .Linux_ARM64 + - .snapshot +Darwin_snapshot: + extends: + - .Darwin + - .snapshot +w32_snapshot: + extends: + - .w32 + - .snapshot +w64_snapshot: + extends: + - .w64 + - .snapshot +package_snapshot: + extends: + - .package + - .snapshot -include: - - https://git.iem.at/pd/iem-ci/raw/master/pd-lib-builder/gitlab-iem.yml diff --git a/deps/aoo/.git-ci/requirements.apt b/deps/aoo/.git-ci/requirements.apt deleted file mode 100644 index 4801f4bf..00000000 --- a/deps/aoo/.git-ci/requirements.apt +++ /dev/null @@ -1 +0,0 @@ -libopus-dev diff --git a/deps/aoo/.git-ci/requirements.brew b/deps/aoo/.git-ci/requirements.brew deleted file mode 100644 index 3f85f81e..00000000 --- a/deps/aoo/.git-ci/requirements.brew +++ /dev/null @@ -1 +0,0 @@ -brew "opus" diff --git a/deps/aoo/.git-ci/requirements.msys2 b/deps/aoo/.git-ci/requirements.msys2 deleted file mode 100644 index 00572003..00000000 --- a/deps/aoo/.git-ci/requirements.msys2 +++ /dev/null @@ -1 +0,0 @@ -@MINGW@opus diff --git a/deps/aoo/.gitattributes b/deps/aoo/.gitattributes index 0793be4f..bb75d008 100644 --- a/deps/aoo/.gitattributes +++ b/deps/aoo/.gitattributes @@ -14,4 +14,4 @@ # Denote all files that are truly binary and should not be modified. *.png binary *.jpg binary -*.ttf binary +*.ttf binary \ No newline at end of file diff --git a/deps/aoo/.gitignore b/deps/aoo/.gitignore index 9dc7134b..7122bbe9 100644 --- a/deps/aoo/.gitignore +++ b/deps/aoo/.gitignore @@ -11,3 +11,5 @@ lib/src/lib/oscpack/examples** lib/src/lib/oscpack/tests** lib/src/lib/oscpack/ip** +build** +install** diff --git a/deps/aoo/.gitmodules b/deps/aoo/.gitmodules new file mode 100644 index 00000000..36d16ff8 --- /dev/null +++ b/deps/aoo/.gitmodules @@ -0,0 +1,3 @@ +[submodule "deps/opus"] + path = deps/opus + url = https://github.com/xiph/opus.git diff --git a/deps/aoo/.gitrepo b/deps/aoo/.gitrepo index c4e8632d..93925ad2 100644 --- a/deps/aoo/.gitrepo +++ b/deps/aoo/.gitrepo @@ -5,8 +5,8 @@ ; [subrepo] remote = https://github.com/essej/aoo.git - branch = sono - commit = e67c763c268b636d5b9ad7b5a5fc24b004890700 - parent = 04a75fd9c344e54952c646a32fd68ef319d91dfe + branch = sononew + commit = 8dcef2dbc635d3411f5d8447ddf7c08e09025fb2 + parent = 289ef23bf292718922d9f7012be59e9a3b24a60a method = merge cmdver = 0.4.1 diff --git a/deps/aoo/CMakeLists.txt b/deps/aoo/CMakeLists.txt new file mode 100644 index 00000000..0799a961 --- /dev/null +++ b/deps/aoo/CMakeLists.txt @@ -0,0 +1,182 @@ +cmake_minimum_required (VERSION 2.8) + +include(GNUInstallDirs) + +set(PROJECT "aoo") +message(STATUS "Project: ${PROJECT}") +project(${PROJECT}) + +include (CheckCCompilerFlag) +include (CheckCXXCompilerFlag) + +if(UNIX AND NOT APPLE AND NOT MINGW) + set(LINUX TRUE) +endif() + +if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + set(CMAKE_COMPILER_IS_CLANG 1) +endif() + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type" FORCE) +endif() +message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") + +message(STATUS "CMAKE_INSTALL_PREFIX: ${CMAKE_INSTALL_PREFIX}") + +if(LINUX AND CMAKE_COMPILER_IS_GNUCXX) + option(STATIC_LIBS "link with static libraries (libstdc++ and libgcc)" ON) +endif() +if(MINGW) + option(STATIC_LIBS "link with static libraries (libstdc++, libgcc and phread)" ON) + set(CMAKE_EXECUTABLE_SUFFIX ".exe") +endif() + +# logging +set(LOGLEVEL "WARNING" CACHE STRING "LOGLEVEL") +message(STATUS "LOGLEVEL: ${LOGLEVEL}") +add_definitions("-DAOO_LOGLEVEL=AOO_LOGLEVEL_${LOGLEVEL}") + +# Windows paths +if (WIN32 OR MINGW) + # check if "Program Files (x86)" exists (64-bit Windows) and if we compile for 32-bit + set(_pf_x86 "ProgramFiles(x86)") + if (DEFINED ENV{${_pf_x86}} AND (CMAKE_SIZEOF_VOID_P EQUAL 4)) + set(PROGRAMFILES $ENV{${_pf_x86}}) + else() + set(PROGRAMFILES $ENV{PROGRAMFILES}) + endif() + set(APPDATA $ENV{APPDATA}) + set(LOCALAPPDATA $ENV{LOCALAPPDATA}) +endif() + +# compiler flags +if (CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANG) + add_definitions(-fvisibility=hidden) + + CHECK_CXX_COMPILER_FLAG(-msse HAS_CXX_SSE) + if (HAS_CXX_SSE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse") + endif() + + CHECK_CXX_COMPILER_FLAG(-msse2 HAS_CXX_SSE2) + if (HAS_CXX_SSE2) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse2") + endif() + + CHECK_CXX_COMPILER_FLAG(-msse3 HAS_CXX_SSE3) + if (HAS_CXX_SSE3) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse3") + endif() + + if (FALSE) + CHECK_CXX_COMPILER_FLAG(-msse4 HAS_CXX_SSE4) + if (HAS_CXX_SSE4) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse4") + endif() + endif() + + CHECK_CXX_COMPILER_FLAG(-mfpmath=sse HAS_CXX_FPMATH_SSE) + if (HAS_CXX_FPMATH_SSE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfpmath=sse") + endif() + + if(NATIVE) + add_definitions(-march=native) + endif() + + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -ffast-math -funroll-loops -fomit-frame-pointer") + + if(CMAKE_COMPILER_IS_CLANG) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") + endif() +endif() +if (MINGW) + set(CMAKE_CXX_COMPILER g++) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mstackrealign") +endif() +if (LINUX) + add_definitions("-fPIC") +endif() + +# dependencies +include_directories(deps) + +# oscpack +option(SYSTEM_OSCPACK "use system provided oscpack library" OFF) +if (SYSTEM_OSCPACK) + set(OSCPACK_LIB "oscpack" CACHE STRING "oscpack linker flags") +else() + set(OSCPACK_LIB "oscpack") + add_subdirectory("deps/oscpack") +endif() + +# md5 +option(SYSTEM_MD5 "use system provided md5 library" OFF) +if (SYSTEM_MD5) + set(MD5_LIB "md5" CACHE STRING "md5 linker flags") +else() + set(MD5_LIB "md5") + add_subdirectory("deps/md5") +endif() + +# Opus +option(CODEC_OPUS "use Opus codec" ON) +if (CODEC_OPUS) + add_definitions("-DUSE_CODEC_OPUS=1") + + option(SYSTEM_OPUS "use system provided Opus library" OFF) + if (SYSTEM_OPUS) + set(CODEC_OPUS_LIB "opus" CACHE STRING "Opus linker flags") + else() + include_directories("deps/opus/include") + # use different variable for finding the library, + # because we don't want to change the cache variable + find_library(OPUS_LIB_STATIC "opus" HINTS "deps/opus/lib" REQUIRED) + set(CODEC_OPUS_LIB ${OPUS_LIB_STATIC}) + if (MINGW) + list(APPEND LIBS "ssp") # for fortified functions + endif() + endif() + message(STATUS "Opus library: ${CODEC_OPUS_LIB}") +endif() + +# platform specific linker flags +if (LINUX) + list(APPEND LIBS "-pthread") + if(STATIC_LIBS) + list(APPEND LIBS "-static-libstdc++" "-static-libgcc") + endif() + set(CMAKE_BUILD_RPATH_USE_ORIGIN ON) +endif() +if (MINGW) + if (STATIC_LIBS) + list(APPEND LIBS "-static-libstdc++" "-static-libgcc" "-static -lpthread") + else() + list(APPEND LIBS "-lpthread") + endif() +endif() +if (APPLE) + list(APPEND LIBS "-lpthread") +endif() + +# headers +include_directories(.) +include_directories(include) + +# "aoo" library +set(AOO_SHARED "aoo") +set(AOO_STATIC "aoo_static") +add_subdirectory(aoo) + +# Pd external +option(BUILD_PD_EXTERNAL "build Pd external" ON) +if (BUILD_PD_EXTERNAL) + add_subdirectory(pd) +endif() + +# SC extension +option(BUILD_SC_EXTENSION "build SC extension" ON) +if (BUILD_SC_EXTENSION) + add_subdirectory(sc) +endif() diff --git a/deps/aoo/aoo/CMakeLists.txt b/deps/aoo/aoo/CMakeLists.txt new file mode 100644 index 00000000..e517c2fb --- /dev/null +++ b/deps/aoo/aoo/CMakeLists.txt @@ -0,0 +1,111 @@ +cmake_minimum_required (VERSION 2.8) + +# networking support +option(USE_AOO_NET "build with networking support" ON) + +# compile time options +option(CUSTOM_ALLOCATOR "build with custom allocator support" OFF) + +option(DYNAMIC_RESAMPLING "enable/disable dynamic resampling" ON) +message(STATUS "use dynamic resampling: ${DYNAMIC_RESAMPLING}") + +set(DLL_BANDWIDTH 0.0001 CACHE STRING "default DLL filter bandwidth") +message(STATUS "default DLL filter bandwidth: ${DLL_BANDWIDTH}") + +option(TIMER_CHECK "use timer check" ON) +message(STATUS "timer check: ${TIMER_CHECK}") + +set(TIMER_TOLERANCE 0.25 CACHE STRING "default timer check tolerance") +message(STATUS "default timer check tolerance: ${TIMER_TOLERANCE}") + +option(BINARY_DATA_MSG "use timer check" ON) +message(STATUS "send binary data message: ${BINARY_DATA_MSG}") + +# compile time debugging options +option(DEBUG_MEMORY "debug memory usage" OFF) + +option(DEBUG_DATA "debug memory usage" OFF) + +option(DEBUG_DLL "debug time DLL filter" OFF) + +option(DEBUG_TIMER "debug timer" OFF) + +option(DEBUG_RESAMPLER "debug resampler" OFF) + +option(DEBUG_AUDIO_BUFFER "debug audio buffer" OFF) + +option(DEBUG_JITTER_BUFFER "debug jitter buffer" OFF) + +set(OPTIONS + "-DAOO_CUSTOM_ALLOCATOR=$" + "-DAOO_DYNAMIC_RESAMPLING=$" + "-DAOO_DLL_BANDWIDTH=${DLL_BANDWIDTH}" + "-DAOO_TIMER_CHECK=$" + "-DAOO_TIMER_TOLERANCE=${TIMER_TOLERANCE}" + "-DAOO_BINARY_DATA_MSG=$" + "-DAOO_DEBUG_MEMORY=$" + "-DAOO_DEBUG_DATA=$" + "-DAOO_DEBUG_DLL=$" + "-DAOO_DEBUG_TIMER=$" + "-DAOO_DEBUG_RESAMPLER=$" + "-DAOO_DEBUG_AUDIO_BUFFER=$" + "-DAOO_DEBUG_JITTER_BUFFER=$" +) + +set(INCLUDE "../include/aoo") +set(COMMON "../common") + +file(GLOB AOO_HEADERS "${INCLUDE}/*.h" "${INCLUDE}/*.hpp" + "${INCLUDE}/codec/aoo_pcm.h" "src/*.hpp" "${COMMON}/*.hpp") +file(GLOB AOO_SRC "src/*.cpp" "src/codec/pcm.cpp" "${COMMON}/*.cpp") + +if (CODEC_OPUS) + list(APPEND AOO_HEADERS "${INCLUDE}/codec/aoo_opus.h") + list(APPEND AOO_SRC "src/codec/opus.cpp") +endif() + +if (USE_AOO_NET) + list(APPEND AOO_HEADERS "src/net/server.hpp" "src/net/client.hpp" + "src/net/SLIP.hpp") + list(APPEND AOO_SRC "src/net/client.cpp" "src/net/server.cpp") +endif() + +# static library +add_library(${AOO_STATIC} STATIC ${AOO_HEADERS} ${AOO_SRC}) + +target_compile_definitions(${AOO_STATIC} PUBLIC ${OPTIONS} "-DAOO_STATIC") + +# shared library +option(BUILD_AOO_SHARED "build 'aoo' shared library" ON) +option(BUILD_AOO_STATIC "build 'aoo' static library" ON) + +if (BUILD_AOO_SHARED) + add_library(${AOO_SHARED} SHARED ${AOO_HEADERS} ${AOO_SRC}) + + target_compile_definitions(${AOO_SHARED} PUBLIC "-DAOO_BUILD" ${OPTIONS}) + if (WIN32 OR MINGW) + target_compile_definitions(${AOO_SHARED} PUBLIC "-DDLL_EXPORT") + target_link_libraries(${AOO_SHARED} "ws2_32") + endif() + if (USE_AOO_NET) + target_link_libraries(${AOO_SHARED} ${MD5_LIB}) + endif() + target_link_libraries(${AOO_SHARED} ${OSCPACK_LIB} ${CODEC_OPUS_LIB} ${LIBS}) +endif() + +if (BUILD_AOO_SHARED OR BUILD_AOO_STATIC) + # install library files + if (BUILD_AOO_SHARED) + list(APPEND TARGET_LIST ${AOO_SHARED}) + endif() + if (BUILD_AOO_STATIC) + list(APPEND TARGET_LIST ${AOO_STATIC}) + endif() + install(TARGETS ${TARGET_LIST} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + # install public headers (note the trailing slash!) + install(DIRECTORY "${INCLUDE}/" + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${AOO_SHARED}) +endif() diff --git a/deps/aoo/aoo/src/aoo.cpp b/deps/aoo/aoo/src/aoo.cpp new file mode 100644 index 00000000..02149016 --- /dev/null +++ b/deps/aoo/aoo/src/aoo.cpp @@ -0,0 +1,440 @@ +#include "aoo/aoo.h" +#if USE_AOO_NET +#include "aoo/aoo_net.h" +#include "common/net_utils.hpp" +#endif + +#include "imp.hpp" +#include "codec.hpp" + +#include "common/sync.hpp" +#include "common/time.hpp" +#include "common/utils.hpp" + +#include "aoo/codec/aoo_pcm.h" +#if USE_CODEC_OPUS +#include "aoo/codec/aoo_opus.h" +#endif + +#include +#include +#include +#include + +namespace aoo { + +/*//////////////////// helper /////////////////////////*/ + +char * copy_string(const char * s){ + if (s){ + auto len = strlen(s); + auto result = aoo::allocate(len + 1); + memcpy(result, s, len + 1); + return (char *)result; + } else { + return nullptr; + } +} + +void free_string(char *s){ + if (s){ + auto len = strlen(s); + aoo::deallocate(s, len + 1); + } +} + +void * copy_sockaddr(const void *sa, int32_t len){ + if (sa){ + auto result = aoo::allocate(len); + memcpy(result, sa, len); + return result; + } else { + return nullptr; + } +} + +void free_sockaddr(void *sa, int32_t len){ + if (sa){ + aoo::deallocate(sa, len); + } +} + +} // aoo + +/*//////////////////// allocator /////////////////////*/ + +#if AOO_CUSTOM_ALLOCATOR || AOO_DEBUG_MEMORY + +namespace aoo { + +#if AOO_DEBUG_MEMORY +std::atomic total_memory{0}; +#endif + +static aoo_allocator g_allocator { + [](size_t n, void *){ + #if AOO_DEBUG_MEMORY + auto total = total_memory.fetch_add(n, std::memory_order_relaxed) + n; + fprintf(stderr, "allocate %d bytes (total: %d)\n", n, total); + fflush(stderr); + #endif + return operator new(n); + }, + nullptr, + [](void *ptr, size_t n, void *){ + #if AOO_DEBUG_MEMORY + auto total = total_memory.fetch_sub(n, std::memory_order_relaxed) - n; + fprintf(stderr, "deallocate %d bytes (total: %d)\n", n, total); + fflush(stderr); + #endif + operator delete(ptr); + }, + nullptr +}; + +void * allocate(size_t size){ + return g_allocator.alloc(size, g_allocator.context); +} + +void deallocate(void *ptr, size_t size){ + g_allocator.free(ptr, size, g_allocator.context); +} + +} // aoo + +#endif + +#if AOO_CUSTOM_ALLOCATOR +void aoo_set_allocator(const aoo_allocator *alloc){ + aoo::g_allocator = *alloc; +} +#endif + +/*//////////////////// Log ////////////////////////////*/ + +#define LOG_MUTEX 1 + +#if LOG_MUTEX +static aoo::sync::mutex g_log_mutex; +#endif + +static void cerr_logfunction(const char *msg, int32_t level, void *ctx){ +#if LOG_MUTEX + aoo::sync::scoped_lock lock(g_log_mutex); +#endif + std::cerr << msg; + std::flush(std::cerr); +} + +static aoo_logfunction g_logfunction = cerr_logfunction; +static void *g_logcontext = nullptr; + +void aoo_set_logfunction(aoo_logfunction f, void *context){ + g_logfunction = f; + g_logcontext = context; +} + +static const char *errmsg[] = { + // TODO + "undefined" +}; + +const char *aoo_error_string(aoo_error e){ + if (e == AOO_OK){ + return "no error"; + } else { + return "unspecified error"; // TODO + } +} + +namespace aoo { + +Log::~Log(){ + stream_ << "\n"; + std::string msg = stream_.str(); + g_logfunction(msg.c_str(), level_, g_logcontext); +} + +} + +/*//////////////////// OSC ////////////////////////////*/ + +aoo_error aoo_parse_pattern(const char *msg, int32_t n, + aoo_type *type, aoo_id *id, int32_t *offset) +{ + int32_t count = 0; + if (n >= AOO_BIN_MSG_HEADER_SIZE && + !memcmp(msg, AOO_BIN_MSG_DOMAIN, AOO_BIN_MSG_DOMAIN_SIZE)) + { + // domain (int32), type (int16), cmd (int16), id (int32) ... + *type = aoo::from_bytes(msg + 4); + // cmd = aoo::from_bytes(msg + 6); + *id = aoo::from_bytes(msg + 8); + *offset = 12; + + return AOO_OK; + } else if (n >= AOO_MSG_DOMAIN_LEN + && !memcmp(msg, AOO_MSG_DOMAIN, AOO_MSG_DOMAIN_LEN)) + { + count += AOO_MSG_DOMAIN_LEN; + if (n >= (count + AOO_MSG_SOURCE_LEN) + && !memcmp(msg + count, AOO_MSG_SOURCE, AOO_MSG_SOURCE_LEN)) + { + *type = AOO_TYPE_SOURCE; + count += AOO_MSG_SOURCE_LEN; + } else if (n >= (count + AOO_MSG_SINK_LEN) + && !memcmp(msg + count, AOO_MSG_SINK, AOO_MSG_SINK_LEN)) + { + *type = AOO_TYPE_SINK; + count += AOO_MSG_SINK_LEN; + } else { + #if USE_AOO_NET + if (n >= (count + AOO_NET_MSG_CLIENT_LEN) + && !memcmp(msg + count, AOO_NET_MSG_CLIENT, AOO_NET_MSG_CLIENT_LEN)) + { + *type = AOO_TYPE_CLIENT; + count += AOO_NET_MSG_CLIENT_LEN; + } else if (n >= (count + AOO_NET_MSG_SERVER_LEN) + && !memcmp(msg + count, AOO_NET_MSG_SERVER, AOO_NET_MSG_SERVER_LEN)) + { + *type = AOO_TYPE_SERVER; + count += AOO_NET_MSG_SERVER_LEN; + } else if (n >= (count + AOO_NET_MSG_PEER_LEN) + && !memcmp(msg + count, AOO_NET_MSG_PEER, AOO_NET_MSG_PEER_LEN)) + { + *type = AOO_TYPE_PEER; + count += AOO_NET_MSG_PEER_LEN; + } else if (n >= (count + AOO_NET_MSG_RELAY_LEN) + && !memcmp(msg + count, AOO_NET_MSG_RELAY, AOO_NET_MSG_RELAY_LEN)) + { + *type = AOO_TYPE_RELAY; + count += AOO_NET_MSG_RELAY_LEN; + } else { + return AOO_ERROR_UNSPECIFIED; + } + + if (offset){ + *offset = count; + } + #endif // USE_AOO_NET + + return AOO_OK; + } + + // /aoo/source or /aoo/sink + if (id){ + int32_t skip = 0; + if (sscanf(msg + count, "/%d%n", id, &skip) > 0){ + count += skip; + } else { + // TODO only print relevant part of OSC address string + LOG_ERROR("aoo_parse_pattern: bad ID " << (msg + count)); + return AOO_ERROR_UNSPECIFIED; + } + } else { + return AOO_ERROR_UNSPECIFIED; + } + + if (offset){ + *offset = count; + } + return AOO_OK; + } else { + return AOO_ERROR_UNSPECIFIED; // not an AoO message + } +} + +// OSC time stamp (NTP time) +uint64_t aoo_osctime_now(void){ + return aoo::time_tag::now(); +} + +double aoo_osctime_to_seconds(uint64_t t){ + return aoo::time_tag(t).to_seconds(); +} + +uint64_t aoo_osctime_from_seconds(double s){ + return aoo::time_tag::from_seconds(s); +} + +double aoo_osctime_duration(uint64_t t1, uint64_t t2){ + return aoo::time_tag::duration(t1, t2); +} + +/*/////////////// version ////////////////////*/ + +void aoo_version(int32_t *major, int32_t *minor, + int32_t *patch, int32_t *pre){ + if (major) *major = AOO_VERSION_MAJOR; + if (minor) *minor = AOO_VERSION_MINOR; + if (patch) *patch = AOO_VERSION_PATCH; + if (pre) *pre = AOO_VERSION_PRERELEASE; +} + +#define STR_HELPER(x) #x +#define STR(x) STR_HELPER(x) + +const char *aoo_version_string(){ + return STR(AOO_VERSION_MAJOR) "." STR(AOO_VERSION_MINOR) + #if AOO_VERSION_PATCH > 0 + "." STR(AOO_VERSION_PATCH) + #endif + #if AOO_VERSION_PRERELEASE > 0 + "-pre" STR(AOO_VERSION_PRERELEASE) + #endif + ; +} + +namespace aoo { + +bool check_version(uint32_t version){ + auto major = (version >> 24) & 255; + auto minor = (version >> 16) & 255; + auto bugfix = (version >> 8) & 255; + + if (major != AOO_VERSION_MAJOR){ + return false; + } + + return true; +} + +uint32_t make_version(){ + // make version: major, minor, bugfix, [protocol] + return ((uint32_t)AOO_VERSION_MAJOR << 24) | ((uint32_t)AOO_VERSION_MINOR << 16) + | ((uint32_t)AOO_VERSION_PATCH << 8); +} + +/*//////////////////// memory /////////////////*/ + +memory_block * memory_block::allocate(size_t size){ + auto fullsize = sizeof(memory_block::header) + size; + auto mem = (memory_block *)aoo::allocate(fullsize); + mem->header.next = nullptr; + mem->header.size = size; +#if DEBUG_MEMORY + fprintf(stderr, "allocate memory block (%d bytes)\n", size); + fflush(stderr); +#endif + return mem; +} + +void memory_block::free(memory_block *mem){ +#if DEBUG_MEMORY + fprintf(stderr, "deallocate memory block (%d bytes)\n", mem->size()); + fflush(stderr); +#endif + aoo::deallocate(mem, mem->full_size()); +} + +memory_list::~memory_list(){ + // free memory blocks + auto mem = memlist_.load(std::memory_order_relaxed); + while (mem){ + auto next = mem->header.next; + memory_block::free(mem); + mem = next; + } +} + +memory_block* memory_list::alloc(size_t size) { + for (;;){ + // try to pop existing block + auto head = memlist_.load(std::memory_order_relaxed); + if (head){ + auto next = head->header.next; + if (memlist_.compare_exchange_weak(head, next, std::memory_order_acq_rel)){ + if (head->header.size >= size){ + #if DEBUG_MEMORY + fprintf(stderr, "reuse memory block (%d bytes)\n", head->header.size); + fflush(stderr); + #endif + return head; + } else { + // free block + memory_block::free(head); + } + } else { + // try again + continue; + } + } + // allocate new block + return memory_block::allocate(size); + } +} +void memory_list::free(memory_block* b) { + b->header.next = memlist_.load(std::memory_order_relaxed); + // check if the head has changed and update it atomically. + // (if the CAS fails, 'next' is updated to the current head) + while (!memlist_.compare_exchange_weak(b->header.next, b, std::memory_order_acq_rel)) ; +#if DEBUG_MEMORY + fprintf(stderr, "return memory block (%d bytes)\n", b->header.size); + fflush(stderr); +#endif +} + +} // aoo + +/*//////////////////// codec //////////////////*/ + + +namespace aoo { + +static std::unordered_map> g_codec_dict; + +const aoo::codec * find_codec(const char * name){ + auto it = g_codec_dict.find(name); + if (it != g_codec_dict.end()){ + return it->second.get(); + } else { + return nullptr; + } +} + +} // aoo + +aoo_error aoo_register_codec(const char *name, const aoo_codec *codec){ + if (aoo::g_codec_dict.count(name) != 0){ + LOG_WARNING("aoo: codec " << name << " already registered!"); + return AOO_ERROR_UNSPECIFIED; + } + aoo::g_codec_dict[name] = std::make_unique(codec); + LOG_VERBOSE("aoo: registered codec '" << name << "'"); + return AOO_OK; +} + +/*/////////////// (de)initialize //////////////////*/ + +void aoo_codec_pcm_setup(aoo_codec_registerfn fn, const aoo_allocator *alloc); +#if USE_CODEC_OPUS +void aoo_codec_opus_setup(aoo_codec_registerfn fn, const aoo_allocator *alloc); +#endif + +#if AOO_CUSTOM_ALLOCATOR || AOO_DEBUG_MEMORY +#define ALLOCATOR &aoo::g_allocator +#else +#define ALLOCATOR nullptr +#endif + +void aoo_initialize(){ + static bool initialized = false; + if (!initialized){ + #if USE_AOO_NET + aoo::socket_init(); + #endif + + // register codecs + aoo_codec_pcm_setup(aoo_register_codec, ALLOCATOR); + + #if USE_CODEC_OPUS + aoo_codec_opus_setup(aoo_register_codec, ALLOCATOR); + #endif + + initialized = true; + } +} + +void aoo_terminate() {} + + diff --git a/deps/aoo/aoo/src/buffer.cpp b/deps/aoo/aoo/src/buffer.cpp new file mode 100644 index 00000000..e30da8d0 --- /dev/null +++ b/deps/aoo/aoo/src/buffer.cpp @@ -0,0 +1,388 @@ +#include "buffer.hpp" + +#include "common/utils.hpp" + +#include +#include + +namespace aoo { + +/*////////////////////////// sent_block /////////////////////////////*/ + +void sent_block::set(int32_t seq, double sr, + const char *data, int32_t nbytes, + int32_t nframes, int32_t framesize) +{ + sequence = seq; + samplerate = sr; + numframes_ = nframes; + framesize_ = framesize; + buffer_.assign(data, data + nbytes); +} + +int32_t sent_block::get_frame(int32_t which, char *data, int32_t n){ + assert(framesize_ > 0 && numframes_ > 0); + if (which >= 0 && which < numframes_){ + auto onset = which * framesize_; + auto minsize = (which == numframes_ - 1) ? size() - onset : framesize_; + if (n >= minsize){ + int32_t nbytes; + if (which == numframes_ - 1){ // last frame + nbytes = size() - onset; + } else { + nbytes = framesize_; + } + auto ptr = buffer_.data() + onset; + std::copy(ptr, ptr + n, data); + return nbytes; + } else { + LOG_ERROR("buffer too small! got " << n << ", need " << minsize); + } + } else { + LOG_ERROR("frame number " << which << " out of range!"); + } + return 0; +} + +int32_t sent_block::frame_size(int32_t which) const { + assert(which < numframes_); + if (which == numframes_ - 1){ // last frame + return size() - which * framesize_; + } else { + return framesize_; + } +} + +/*////////////////////////// history_buffer ///////////////////////////*/ + +void history_buffer::clear(){ + head_ = 0; + size_ = 0; +} + +void history_buffer::resize(int32_t n){ + buffer_.resize(n); + clear(); +} + +sent_block * history_buffer::find(int32_t seq){ + // the code below only works if the buffer is not empty! + if (size_ > 0){ + // check if sequence number is outdated + // (tail always starts at buffer begin and becomes + // equal to head once the buffer is full) + auto head = buffer_.begin() + head_; + auto tail = (size_ == capacity()) ? head : buffer_.begin(); + if (seq < tail->sequence){ + LOG_DEBUG("history buffer: block " << seq << " too old"); + return nullptr; + } + + #if 0 + // linear search + auto end = buffer_.begin() + size_; + for (auto it = buffer_.begin(); it != end; ++it){ + if (it->sequence == seq){ + return &(*it); + } + } + #else + // binary search + auto dofind = [&](auto begin, auto end) -> sent_block * { + auto result = std::lower_bound(begin, end, seq, [](auto& a, auto& b){ + return a.sequence < b; + }); + if (result != end && result->sequence == seq){ + return &(*result); + } else { + return nullptr; + } + }; + if (head != tail){ + // buffer not full, just search range [tail, head] + auto result = dofind(tail, head); + if (result){ + return result; + } + } else { + // blocks are always pushed in chronological order, + // so the ranges [begin, head] and [head, end] will always be sorted. + auto result = dofind(buffer_.begin(), head); + if (!result){ + result = dofind(head, buffer_.end()); + } + if (result){ + return result; + } + } + #endif + } + + LOG_ERROR("history buffer: couldn't find block " << seq); + return nullptr; +} + +sent_block * history_buffer::push() +{ + assert(!buffer_.empty()); + auto old = head_++; + if (head_ >= capacity()){ + head_ = 0; + } + if (size_ < capacity()){ + ++size_; + } + return &buffer_[old]; +} + +/*////////////////////// received_block //////////////////////*/ + +void received_block::reserve(int32_t size){ + buffer_.reserve(size); +} + +void received_block::init(int32_t seq, double sr, int32_t chn, + int32_t nbytes, int32_t nframes) +{ + assert(nbytes > 0); + assert(nframes <= (int32_t)frames_.size()); + // keep timestamp and numtries if we're actually reiniting + if (seq != sequence){ + timestamp_ = 0; + numtries_ = 0; + } + sequence = seq; + samplerate = sr; + channel = chn; + buffer_.resize(nbytes); + numframes_ = nframes; + framesize_ = 0; + dropped_ = false; + frames_.reset(); + for (int i = 0; i < nframes; ++i){ + frames_[i] = true; + } +} + +void received_block::init(int32_t seq, bool dropped) +{ + sequence = seq; + samplerate = 0; + channel = 0; + buffer_.clear(); + numframes_ = 0; + framesize_ = 0; + timestamp_ = 0; + numtries_ = 0; + dropped_ = dropped; + if (dropped){ + frames_.reset(); // complete + } else { + frames_.set(); // has_frame() always returns false + } +} + +bool received_block::dropped() const { + return dropped_; +} + +bool received_block::complete() const { + return frames_.none(); +} + +int32_t received_block::count_frames() const { + return std::max(0, numframes_ - frames_.count()); +} + +int32_t received_block::resend_count() const { + return numtries_; +} + +void received_block::add_frame(int32_t which, const char *data, int32_t n){ + assert(!buffer_.empty()); + assert(which < numframes_); + if (which == numframes_ - 1){ + #if AOO_DEBUG_JITTER_BUFFER + DO_LOG_DEBUG("jitter buffer: copy last frame with " << n << " bytes"); + #endif + std::copy(data, data + n, buffer_.end() - n); + } else { + #if AOO_DEBUG_JITTER_BUFFER + DO_LOG_DEBUG("jitter buffer: copy frame " << which << " with " << n << " bytes"); + #endif + std::copy(data, data + n, buffer_.data() + (which * n)); + framesize_ = n; // LATER allow varying framesizes + } + frames_[which] = false; +} + +bool received_block::has_frame(int32_t which) const { + return !frames_[which]; +} + +bool received_block::update(double time, double interval){ + if (timestamp_ > 0 && (time - timestamp_) < interval){ + return false; + } + timestamp_ = time; + numtries_++; +#if AOO_DEBUG_JITTER_BUFFER + DO_LOG_DEBUG( + "jitter buffer: request block " << sequence); +#endif + return true; +} + +/*////////////////////////// jitter_buffer /////////////////////////////*/ + +void jitter_buffer::clear(){ + head_ = tail_ = size_ = 0; + last_popped_ = last_pushed_ = -1; +} + +void jitter_buffer::resize(int32_t n, int32_t maxblocksize){ + data_.resize(n); + for (auto& b : data_){ + b.reserve(maxblocksize); + } + clear(); +} + +received_block* jitter_buffer::find(int32_t seq){ + // first try the end, as we most likely have to complete the most recent block + if (empty()){ + return nullptr; + } else if (back().sequence == seq){ + return &back(); + } +#if 0 + // linear search + if (head_ > tail_){ + for (int32_t i = tail_; i < head_; ++i){ + if (data_[i].sequence == seq){ + return &data_[i]; + } + } + } else { + for (int32_t i = 0; i < head_; ++i){ + if (data_[i].sequence == seq){ + return &data_[i]; + } + } + for (int32_t i = tail_; i < capacity(); ++i){ + if (data_[i].sequence == seq){ + return &data_[i]; + } + } + } + return nullptr; +#else + // binary search + // (blocks are always pushed in chronological order) + auto dofind = [&](auto begin, auto end) -> received_block * { + auto result = std::lower_bound(begin, end, seq, [](auto& a, auto& b){ + return a.sequence < b; + }); + if (result != end && result->sequence == seq){ + return &(*result); + } else { + return nullptr; + } + }; + + auto begin = data_.data(); + if (head_ > tail_){ + // [tail, head] + return dofind(begin + tail_, begin + head_); + } else { + // [begin, head] + [tail, end] + auto result = dofind(begin, begin + head_); + if (!result){ + result = dofind(begin + tail_, begin + data_.capacity()); + } + return result; + } +#endif +} + +received_block* jitter_buffer::push_back(int32_t seq){ + assert(!full()); + auto old = head_; + if (++head_ == capacity()){ + head_ = 0; + } + size_++; + last_pushed_ = seq; + return &data_[old]; +} + +void jitter_buffer::pop_front(){ + assert(!empty()); + last_popped_ = data_[tail_].sequence; + if (++tail_ == capacity()){ + tail_ = 0; + } + size_--; +} + +received_block& jitter_buffer::front(){ + assert(!empty()); + return data_[tail_]; +} + +const received_block& jitter_buffer::front() const { + assert(!empty()); + return data_[tail_]; +} + +received_block& jitter_buffer::back(){ + assert(!empty()); + auto index = head_ - 1; + if (index < 0){ + index = capacity() - 1; + } + return data_[index]; +} + +const received_block& jitter_buffer::back() const { + assert(!empty()); + auto index = head_ - 1; + if (index < 0){ + index = capacity() - 1; + } + return data_[index]; +} + +jitter_buffer::iterator jitter_buffer::begin(){ + if (empty()){ + return end(); + } else { + return iterator(this, &data_[tail_]); + } +} + +jitter_buffer::const_iterator jitter_buffer::begin() const { + if (empty()){ + return end(); + } else { + return const_iterator(this, &data_[tail_]); + } +} + +jitter_buffer::iterator jitter_buffer::end(){ + return iterator(this); +} + +jitter_buffer::const_iterator jitter_buffer::end() const { + return const_iterator(this); +} + +std::ostream& operator<<(std::ostream& os, const jitter_buffer& jb){ + os << "jitterbuffer (" << jb.size() << " / " << jb.capacity() << "): "; + for (auto& b : jb){ + os << b.sequence << " " << "(" << b.count_frames() << "/" << b.num_frames() << ") "; + } + return os; +} + +} // aoo diff --git a/deps/aoo/aoo/src/buffer.hpp b/deps/aoo/aoo/src/buffer.hpp new file mode 100644 index 00000000..ae75f1e2 --- /dev/null +++ b/deps/aoo/aoo/src/buffer.hpp @@ -0,0 +1,199 @@ +#pragma once + +#include "aoo/aoo.h" + +#include "imp.hpp" + +#include +#include +#include + +namespace aoo { + +struct data_packet { + int32_t sequence; + int32_t channel; + int32_t totalsize; + int32_t nframes; + int32_t frame; + int32_t size; + const char *data; + double samplerate; +}; + +/*///////////////////// history_buffer /////////////////////////*/ + +class sent_block { +public: + // methods + void set(int32_t seq, double sr, + const char *data, int32_t nbytes, + int32_t nframes, int32_t framesize); + + const char* data() const { return buffer_.data(); } + int32_t size() const { return buffer_.size(); } + + int32_t num_frames() const { return numframes_; } + int32_t frame_size(int32_t which) const; + int32_t get_frame(int32_t which, char * data, int32_t n); + + // data + int32_t sequence = -1; + double samplerate = 0; +protected: + std::vector> buffer_; + int32_t numframes_ = 0; + int32_t framesize_ = 0; +}; + +class history_buffer { +public: + void clear(); + bool empty() const { + return size_ == 0; + } + int32_t size() const { + return size_; + } + int32_t capacity() const { + return buffer_.size(); + } + void resize(int32_t n); + sent_block * find(int32_t seq); + sent_block * push(); +private: + using block_buffer = std::vector>; + block_buffer buffer_; + int32_t head_ = 0; + int32_t size_ = 0; +}; + +/*///////////////// jitter_buffer ///////////////////////*/ + +class received_block { +public: + void reserve(int32_t size); + + void init(int32_t seq, bool dropped); + void init(int32_t seq, double sr, int32_t chn, + int32_t nbytes, int32_t nframes); + + const char* data() const { return buffer_.data(); } + int32_t size() const { return buffer_.size(); } + + int32_t num_frames() const { return numframes_; } + bool has_frame(int32_t which) const; + int32_t count_frames() const; + void add_frame(int32_t which, const char *data, int32_t n); + + int32_t resend_count() const; + bool dropped() const; + bool complete() const; + bool update(double time, double interval); + + // data + int32_t sequence = -1; + int32_t channel = 0; + double samplerate = 0; +protected: + std::vector> buffer_; + int32_t numframes_ = 0; + int32_t framesize_ = 0; + std::bitset<256> frames_ = 0; + double timestamp_ = 0; + int32_t numtries_ = 0; + bool dropped_ = false; +}; + +class jitter_buffer { +public: + template + class base_iterator { + T *data_; + U *owner_; + public: + base_iterator(U* owner) + : data_(nullptr), owner_(owner){} + base_iterator(U* owner, T* data) + : data_(data), owner_(owner){} + base_iterator(const base_iterator&) = default; + base_iterator& operator=(const base_iterator&) = default; + T& operator*() { return *data_; } + T* operator->() { return data_; } + base_iterator& operator++() { + auto begin = owner_->data_.data(); + auto end = begin + owner_->data_.size(); + auto next = data_ + 1; + if (next == end){ + next = begin; + } + if (next == (begin + owner_->head_)){ + next = nullptr; // sentinel + } + data_ = next; + return *this; + } + base_iterator operator++(int) { + base_iterator old = *this; + operator++(); + return old; + } + bool operator==(const base_iterator& other){ + return data_ == other.data_; + } + bool operator!=(const base_iterator& other){ + return data_ != other.data_; + } + }; + + using iterator = base_iterator; + using const_iterator = base_iterator; + + void clear(); + void resize(int32_t n, int32_t maxblocksize); + + bool empty() const { + return size_ == 0; + } + bool full() const { + return size_ == capacity(); + } + int32_t size() const { + return size_; + } + int32_t capacity() const { + return data_.size(); + } + + received_block* find(int32_t seq); + received_block* push_back(int32_t seq); + void pop_front(); + + int32_t last_pushed() const { + return last_pushed_; + } + int32_t last_popped() const { + return last_popped_; + } + + received_block& front(); + const received_block& front() const; + received_block& back(); + const received_block& back() const; + + iterator begin(); + const_iterator begin() const; + iterator end(); + const_iterator end() const; + + friend std::ostream& operator<<(std::ostream& os, const jitter_buffer& b); +private: + std::vector> data_; + int32_t size_ = 0; + int32_t head_ = 0; + int32_t tail_ = 0; + int32_t last_pushed_ = -1; + int32_t last_popped_ = -1; +}; + +} // aoo diff --git a/deps/aoo/aoo/src/codec.hpp b/deps/aoo/aoo/src/codec.hpp new file mode 100644 index 00000000..48eac9ad --- /dev/null +++ b/deps/aoo/aoo/src/codec.hpp @@ -0,0 +1,145 @@ +#pragma once + +#include "aoo/aoo.h" +#include "imp.hpp" + +#include + +namespace aoo { + +class encoder; +class decoder; + +class codec { +public: + codec(const aoo_codec *c) + : codec_(c){} + + const char *name() const { + return codec_->name; + } + + std::unique_ptr create_encoder() const; + + std::unique_ptr create_decoder() const; + + aoo_error serialize(const aoo_format& f, + char *buf, int32_t &n) const { + return codec_->serialize(&f, buf, &n); + } + + aoo_error deserialize(const aoo_format& header, + const char *data, int32_t n, + aoo_format& f, int32_t size) const { + return codec_->deserialize(&header, data, n, &f, size); + } +protected: + const aoo_codec *codec_; +}; + +class base_codec : public codec { +public: + base_codec(const aoo_codec *c, void *obj) + : codec(c), obj_(obj){} + base_codec(const aoo_codec&) = delete; + + int32_t nchannels() const { return nchannels_; } + + int32_t samplerate() const { return samplerate_; } + + int32_t blocksize() const { return blocksize_; } +protected: + void *obj_; + int32_t nchannels_ = 0; + int32_t samplerate_ = 0; + int32_t blocksize_ = 0; + + void save_format(const aoo_format& f){ + nchannels_ = f.nchannels; + samplerate_ = f.samplerate; + blocksize_ = f.blocksize; + } +}; + +class encoder : public base_codec { +public: + using base_codec::base_codec; + + ~encoder(){ + codec_->encoder_free(obj_); + } + + aoo_error set_format(aoo_format& fmt){ + auto result = codec_->encoder_ctl(obj_, + AOO_CODEC_SET_FORMAT, &fmt, sizeof(aoo_format)); + if (result == AOO_OK){ + save_format(fmt); // after validation! + } + return result; + } + + aoo_error get_format(aoo_format& fmt, size_t size) const { + return codec_->encoder_ctl(obj_, AOO_CODEC_GET_FORMAT, + &fmt, size); + } + + bool compare(const aoo_format& fmt) const { + return codec_->encoder_ctl(obj_, AOO_CODEC_FORMAT_EQUAL, + (void *)&fmt, fmt.size); + } + + aoo_error reset() { + return codec_->encoder_ctl(obj_, AOO_CODEC_RESET, nullptr, 0); + } + + aoo_error encode(const aoo_sample *s, int32_t n, char *buf, int32_t &size){ + return codec_->encoder_encode(obj_, s, n, buf, &size); + } +}; + +inline std::unique_ptr codec::create_encoder() const { + return std::make_unique(codec_, codec_->encoder_new()); +} + +class decoder : public base_codec { +public: + using base_codec::base_codec; + ~decoder(){ + codec_->decoder_free(obj_); + } + + aoo_error set_format(aoo_format& fmt){ + auto result = codec_->decoder_ctl(obj_, + AOO_CODEC_SET_FORMAT, &fmt, sizeof(aoo_format)); + if (result == AOO_OK){ + save_format(fmt); // after validation! + } + return result; + } + + aoo_error get_format(aoo_format& fmt, size_t size) const { + return codec_->decoder_ctl(obj_, AOO_CODEC_GET_FORMAT, + &fmt, size); + } + + bool compare(const aoo_format& fmt) const { + return codec_->decoder_ctl(obj_, AOO_CODEC_FORMAT_EQUAL, + (void *)&fmt, fmt.size); + } + + aoo_error reset() { + return codec_->decoder_ctl(obj_, AOO_CODEC_RESET, nullptr, 0); + } + + aoo_error decode(const char *buf, int32_t size, aoo_sample *s, int32_t &n){ + return codec_->decoder_decode(obj_, buf, size, s, &n); + } +}; + +inline std::unique_ptr codec::create_decoder() const { + return std::make_unique(codec_, codec_->decoder_new()); +} + +const codec * find_codec(const char * name); + +} // aoo diff --git a/deps/aoo/aoo/src/codec/opus.cpp b/deps/aoo/aoo/src/codec/opus.cpp new file mode 100644 index 00000000..a9ef2de8 --- /dev/null +++ b/deps/aoo/aoo/src/codec/opus.cpp @@ -0,0 +1,499 @@ +/* Copyright (c) 2010-Now Christof Ressi, Winfried Ritsch and others. + * For information on usage and redistribution, and for a DISCLAIMER OF ALL + * WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ + +#include "aoo/codec/aoo_opus.h" +#include "common/utils.hpp" + +#include +#include +#include + +namespace { + +aoo_allocator g_allocator { + [](size_t n, void *){ return operator new(n); }, + nullptr, + [](void *ptr, size_t, void *){ operator delete(ptr); }, + nullptr +}; + +void *allocate(size_t n){ + return g_allocator.alloc(n, g_allocator.context); +} + +void deallocate(void *ptr, size_t n){ + g_allocator.free(ptr, n, g_allocator.context); +} + +void print_settings(const aoo_format_opus& f){ + const char *application, *type; + + switch (f.application_type){ + case OPUS_APPLICATION_VOIP: + application = "VOIP"; + break; + case OPUS_APPLICATION_RESTRICTED_LOWDELAY: + application = "low delay"; + break; + default: + application = "audio"; + break; + } + + switch (f.signal_type){ + case OPUS_SIGNAL_MUSIC: + type = "music"; + break; + case OPUS_SIGNAL_VOICE: + type = "voice"; + break; + default: + type = "auto"; + break; + } + + LOG_VERBOSE("Opus settings: " + << "nchannels = " << f.header.nchannels + << ", blocksize = " << f.header.blocksize + << ", samplerate = " << f.header.samplerate + << ", application = " << application + << ", bitrate = " << f.bitrate + << ", complexity = " << f.complexity + << ", signal type = " << type); +} + +/*/////////////////////// codec base ////////////////////////*/ + +struct codec { + codec(){ + memset(&format, 0, sizeof(format)); + } + aoo_format_opus format; +}; + +void validate_format(aoo_format_opus& f, bool loud = true) +{ + f.header.codec = AOO_CODEC_OPUS; // static string! + f.header.size = sizeof(aoo_format_opus); // actual size! + // validate samplerate + switch (f.header.samplerate){ + case 8000: + case 12000: + case 16000: + case 24000: + case 48000: + break; + default: + if (loud){ + LOG_VERBOSE("Opus: samplerate " << f.header.samplerate + << " not supported - using 48000"); + } + f.header.samplerate = 48000; + break; + } + // validate channels (LATER support multichannel!) + if (f.header.nchannels < 1 || f.header.nchannels > 255){ + if (loud){ + LOG_WARNING("Opus: channel count " << f.header.nchannels << + " out of range - using 1 channels"); + } + f.header.nchannels = 1; + } + // validate blocksize + const int minblocksize = f.header.samplerate / 400; // 2.5 ms (e.g. 120 samples @ 48 kHz) + const int maxblocksize = minblocksize * 24; // 60 ms (e.g. 2880 samples @ 48 kHz) + int blocksize = f.header.blocksize; + if (blocksize <= minblocksize){ + f.header.blocksize = minblocksize; + } else if (blocksize >= maxblocksize){ + f.header.blocksize = maxblocksize; + } else { + // round down to nearest multiple of 2.5 ms (in power of 2 steps) + int result = minblocksize; + while (result <= blocksize){ + result *= 2; + } + f.header.blocksize = result / 2; + } + // validate application type + if (f.application_type != OPUS_APPLICATION_VOIP + && f.application_type != OPUS_APPLICATION_AUDIO + && f.application_type != OPUS_APPLICATION_RESTRICTED_LOWDELAY) + { + if (loud){ + LOG_WARNING("Opus: bad application type, using OPUS_APPLICATION_AUDIO"); + } + f.application_type = OPUS_APPLICATION_AUDIO; + } + // bitrate, complexity and signal type should be validated by opus +} + +aoo_error compare(codec *c, const aoo_format_opus *fmt) +{ + // copy and validate! + aoo_format_opus f1; + memcpy(&f1, fmt, sizeof(aoo_format_opus)); + + auto& f2 = c->format; + auto& h1 = f1.header; + auto& h2 = f2.header; + + // check before validate()! + if (strcmp(h1.codec, h2.codec) || + h1.size != h2.size) { + return false; + } + + validate_format(f1, false); + + return h1.blocksize == h2.blocksize && + h1.samplerate == h2.samplerate && + h1.nchannels == h2.nchannels && + f1.application_type == f2.application_type && + f1.bitrate == f2.bitrate && + f1.complexity == f2.complexity && + f1.signal_type == f2.signal_type; +} + +aoo_error get_format(codec *c, aoo_format *f, size_t size) +{ + // check if format has been set + if (c->format.header.codec){ + if (size >= c->format.header.size){ + memcpy(f, &c->format, sizeof(aoo_format_opus)); + return AOO_OK; + } else { + return AOO_ERROR_UNSPECIFIED; + } + } else { + return AOO_ERROR_UNSPECIFIED; + } +} + +/*/////////////////////////// encoder //////////////////////*/ + +struct encoder : codec { + ~encoder(){ + if (state){ + deallocate(state, size); + } + } + OpusMSEncoder *state = nullptr; + size_t size = 0; +}; + +void *encoder_new(){ + auto obj = allocate(sizeof(encoder)); + new (obj) encoder {}; + return obj; +} + +void encoder_free(void *enc){ + static_cast(enc)->~encoder(); + deallocate(enc, sizeof(encoder)); +} + +aoo_error encode(void *enc, + const aoo_sample *s, int32_t n, + char *buf, int32_t *size) +{ + auto c = static_cast(enc); + if (c->state){ + auto framesize = n / c->format.header.nchannels; + auto result = opus_multistream_encode_float( + c->state, s, framesize, (unsigned char *)buf, *size); + if (result > 0){ + *size = result; + return AOO_OK; + } else { + LOG_VERBOSE("Opus: opus_encode_float() failed with error code " << result); + } + } + return AOO_ERROR_UNSPECIFIED; +} + +aoo_error encoder_set_format(encoder *c, aoo_format_opus *f){ + if (strcmp(f->header.codec, AOO_CODEC_OPUS)){ + return AOO_ERROR_UNSPECIFIED; + } + if (f->header.size < sizeof(aoo_format_opus)){ + return AOO_ERROR_UNSPECIFIED; + } + + validate_format(*f); + + // LATER only deallocate if channels, sr and application type + // have changed, otherwise simply reset the encoder. + if (c->state){ + deallocate(c->state, c->size); + c->state = nullptr; + c->size = 0; + } + // setup channel mapping + // only use decoupled streams (what's the point of coupled streams?) + auto nchannels = f->header.nchannels; + unsigned char mapping[256]; + for (int i = 0; i < nchannels; ++i){ + mapping[i] = i; + } + memset(mapping + nchannels, 255, 256 - nchannels); + // create state + size_t size = opus_multistream_encoder_get_size(nchannels, 0); + auto state = (OpusMSEncoder *)allocate(size); + if (!state){ + return AOO_ERROR_UNSPECIFIED; + } + auto error = opus_multistream_encoder_init(state, f->header.samplerate, + nchannels, nchannels, 0, mapping, f->application_type); + if (error != OPUS_OK){ + LOG_ERROR("Opus: opus_encoder_create() failed with error code " << error); + return AOO_ERROR_UNSPECIFIED; + } + c->state = state; + c->size = size; + // apply settings + // complexity + opus_multistream_encoder_ctl(c->state, OPUS_SET_COMPLEXITY(f->complexity)); + opus_multistream_encoder_ctl(c->state, OPUS_GET_COMPLEXITY(&f->complexity)); + // bitrate + opus_multistream_encoder_ctl(c->state, OPUS_SET_BITRATE(f->bitrate)); +#if 0 + // This control is broken in opus_multistream_encoder (as of opus v1.3.2) + // because it would always return the default bitrate. + // The only thing we can do is omit the function and just keep the input value. + // This means that clients have to explicitly check for OPUS_AUTO and + // OPUS_BITRATE_MAX when reading the 'bitrate' value after encoder_setformat(). + opus_multistream_encoder_ctl(c->state, OPUS_GET_BITRATE(&f->bitrate)); +#endif + // signal type + opus_multistream_encoder_ctl(c->state, OPUS_SET_SIGNAL(f->signal_type)); + opus_multistream_encoder_ctl(c->state, OPUS_GET_SIGNAL(&f->signal_type)); + + // save and print settings + memcpy(&c->format, f, sizeof(aoo_format_opus)); + print_settings(c->format); + + return AOO_OK; +} + +aoo_error encoder_ctl(void *x, int32_t ctl, void *ptr, int32_t size){ + switch (ctl){ + case AOO_CODEC_SET_FORMAT: + assert(size >= sizeof(aoo_format)); + return encoder_set_format((encoder *)x, (aoo_format_opus *)ptr); + case AOO_CODEC_GET_FORMAT: + return get_format((codec *)x, (aoo_format *)ptr, size); + case AOO_CODEC_RESET: + if (opus_multistream_encoder_ctl(static_cast(x)->state, + OPUS_RESET_STATE) == OPUS_OK) { + return AOO_OK; + } else { + return AOO_ERROR_UNSPECIFIED; + } + case AOO_CODEC_FORMAT_EQUAL: + assert(size >= sizeof(aoo_format)); + return compare((codec *)x, (const aoo_format_opus *)ptr); + default: + LOG_WARNING("Opus: unsupported codec ctl " << ctl); + return AOO_ERROR_UNSPECIFIED; + } +} + +/*/////////////////////// decoder ///////////////////////////*/ + +struct decoder : codec { + ~decoder(){ + if (state){ + deallocate(state, size); + } + } + OpusMSDecoder * state = nullptr; + size_t size = 0; +}; + +void *decoder_new(){ + auto obj = allocate(sizeof(decoder)); + new (obj) decoder {}; + return obj; +} + +void decoder_free(void *dec){ + static_cast(dec)->~decoder(); + deallocate(dec, sizeof(decoder)); +} + +aoo_error decode(void *dec, + const char *buf, int32_t size, + aoo_sample *s, int32_t *n) +{ + auto c = static_cast(dec); + if (c->state){ + auto framesize = *n / c->format.header.nchannels; + auto result = opus_multistream_decode_float( + c->state, (const unsigned char *)buf, size, s, framesize, 0); + if (result > 0){ + *n = result; + return AOO_OK; + } else if (result < 0) { + LOG_VERBOSE("Opus: opus_decode_float() failed with error code " << result); + } + } + return AOO_ERROR_UNSPECIFIED; +} + +aoo_error decoder_set_format(decoder *c, aoo_format *f) +{ + if (strcmp(f->codec, AOO_CODEC_OPUS)){ + return AOO_ERROR_UNSPECIFIED; + } + if (f->size < sizeof(aoo_format_opus)){ + return AOO_ERROR_UNSPECIFIED; + } + + auto fmt = reinterpret_cast(f); + + validate_format(*fmt); + + // LATER only deallocate if channels and sr have changed, + // otherwise simply reset the decoder. + if (c->state){ + deallocate(c->state, c->size); + c->state = nullptr; + c->size = 0; + } + // setup channel mapping + // only use decoupled streams (what's the point of coupled streams?) + auto nchannels = fmt->header.nchannels; + unsigned char mapping[256]; + for (int i = 0; i < nchannels; ++i){ + mapping[i] = i; + } + memset(mapping + nchannels, 255, 256 - nchannels); + // create state + size_t size = opus_multistream_decoder_get_size(nchannels, 0); + auto state = (OpusMSDecoder *)allocate(size); + if (!state){ + return AOO_ERROR_UNSPECIFIED; + } + auto error = opus_multistream_decoder_init(state, fmt->header.samplerate, + nchannels, nchannels, 0, mapping); + if (error != OPUS_OK){ + LOG_ERROR("Opus: opus_decoder_create() failed with error code " << error); + return AOO_ERROR_UNSPECIFIED; + } + c->state = state; + c->size = size; + // these are actually encoder settings and don't do anything on the decoder +#if 0 + // complexity + opus_multistream_decoder_ctl(c->state, OPUS_SET_COMPLEXITY(f->complexity)); + opus_multistream_decoder_ctl(c->state, OPUS_GET_COMPLEXITY(&f->complexity)); + // bitrate + opus_multistream_decoder_ctl(c->state, OPUS_SET_BITRATE(f->bitrate)); + opus_multistream_decoder_ctl(c->state, OPUS_GET_BITRATE(&f->bitrate)); + // signal type + opus_multistream_decoder_ctl(c->state, OPUS_SET_SIGNAL(f->signal_type)); + opus_multistream_decoder_ctl(c->state, OPUS_GET_SIGNAL(&f->signal_type)); +#endif + + // save and print settings + memcpy(&c->format, fmt, sizeof(aoo_format_opus)); + print_settings(c->format); + + return AOO_OK; +} + +aoo_error decoder_ctl(void *x, int32_t ctl, void *ptr, int32_t size){ + switch (ctl){ + case AOO_CODEC_SET_FORMAT: + assert(size >= sizeof(aoo_format)); + return decoder_set_format((decoder *)x, (aoo_format *)ptr); + case AOO_CODEC_GET_FORMAT: + return get_format((decoder *)x, (aoo_format *)ptr, size); + case AOO_CODEC_RESET: + if (opus_multistream_decoder_ctl(static_cast(x)->state, + OPUS_RESET_STATE) == OPUS_OK) { + return AOO_OK; + } else { + return AOO_ERROR_UNSPECIFIED; + } + case AOO_CODEC_FORMAT_EQUAL: + assert(size >= sizeof(aoo_format)); + return compare((codec *)x, (const aoo_format_opus *)ptr); + default: + LOG_WARNING("Opus: unsupported codec ctl " << ctl); + return AOO_ERROR_UNSPECIFIED; + } +} + +/*////////////////////// codec ////////////////////*/ + +aoo_error serialize(const aoo_format *f, char *buf, int32_t *size){ + if (*size >= 16){ + auto fmt = (const aoo_format_opus *)f; + aoo::to_bytes(fmt->bitrate, buf); + aoo::to_bytes(fmt->complexity, buf + 4); + aoo::to_bytes(fmt->signal_type, buf + 8); + aoo::to_bytes(fmt->application_type, buf + 12); + *size = 16; + + return AOO_OK; + } else { + LOG_WARNING("Opus: couldn't write settings"); + return AOO_ERROR_UNSPECIFIED; + } +} + +aoo_error deserialize(const aoo_format *header, const char *buf, + int32_t nbytes, aoo_format *f, int32_t size){ + if (nbytes < 16){ + LOG_ERROR("Opus: couldn't read format - not enough data!"); + return AOO_ERROR_UNSPECIFIED; + } + if (size < sizeof(aoo_format_opus)){ + LOG_ERROR("Opus: output format storage too small"); + return AOO_ERROR_UNSPECIFIED; + } + auto fmt = (aoo_format_opus *)f; + // header + fmt->header.codec = AOO_CODEC_OPUS; // static string! + fmt->header.size = sizeof(aoo_format_opus); // actual size! + fmt->header.blocksize = header->blocksize; + fmt->header.nchannels = header->nchannels; + fmt->header.samplerate = header->samplerate; + // options + fmt->bitrate = aoo::from_bytes(buf); + fmt->complexity = aoo::from_bytes(buf + 4); + fmt->signal_type = aoo::from_bytes(buf + 8); + fmt->application_type = aoo::from_bytes(buf + 12); + + return AOO_OK; +} + +aoo_codec codec_class = { + AOO_CODEC_OPUS, + // encoder + encoder_new, + encoder_free, + encoder_ctl, + encode, + // decoder + decoder_new, + decoder_free, + decoder_ctl, + decode, + // helper + serialize, + deserialize +}; + +} // namespace + +void aoo_codec_opus_setup(aoo_codec_registerfn fn, const aoo_allocator *alloc){ + if (alloc){ + g_allocator = *alloc; + } + fn(AOO_CODEC_OPUS, &codec_class); +} + diff --git a/deps/aoo/aoo/src/codec/pcm.cpp b/deps/aoo/aoo/src/codec/pcm.cpp new file mode 100644 index 00000000..8ccc569c --- /dev/null +++ b/deps/aoo/aoo/src/codec/pcm.cpp @@ -0,0 +1,438 @@ +/* Copyright (c) 2010-Now Christof Ressi, Winfried Ritsch and others. + * For information on usage and redistribution, and for a DISCLAIMER OF ALL + * WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ + +#include "aoo/codec/aoo_pcm.h" +#include "common/utils.hpp" + +#include +#include + +namespace { + +aoo_allocator g_allocator { + [](size_t n, void *){ return operator new(n); }, + nullptr, + [](void *ptr, size_t, void *){ operator delete(ptr); }, + nullptr +}; + +// conversion routines between aoo_sample and PCM data +union convert { + int8_t b[8]; + int16_t i16; + int32_t i32; + int64_t i64; + float f; + double d; +}; + +int32_t bytes_per_sample(int32_t bd) +{ + switch (bd){ + case AOO_PCM_INT16: + return 2; + case AOO_PCM_INT24: + return 3; + case AOO_PCM_FLOAT32: + return 4; + case AOO_PCM_FLOAT64: + return 8; + default: + assert(false); + return 0; + } +} + +void sample_to_int16(aoo_sample in, char *out) +{ + convert c; + int32_t temp = in * 0x7fff + 0.5f; + c.i16 = (temp > INT16_MAX) ? INT16_MAX : (temp < INT16_MIN) ? INT16_MIN : temp; +#if BYTE_ORDER == BIG_ENDIAN + memcpy(out, c.b, 2); // optimized away +#else + out[0] = c.b[1]; + out[1] = c.b[0]; +#endif +} + +void sample_to_int24(aoo_sample in, char *out) +{ + convert c; + int32_t temp = in * 0x7fffffff + 0.5f; + c.i32 = (temp > INT32_MAX) ? INT32_MAX : (temp < INT32_MIN) ? INT32_MIN : temp; + // only copy the highest 3 bytes! +#if BYTE_ORDER == BIG_ENDIAN + out[0] = c.b[0]; + out[1] = c.b[1]; + out[2] = c.b[2]; +#else + out[0] = c.b[3]; + out[1] = c.b[2]; + out[2] = c.b[1]; +#endif +} + +void sample_to_float32(aoo_sample in, char *out) +{ + aoo::to_bytes(in, out); +} + +void sample_to_float64(aoo_sample in, char *out) +{ + aoo::to_bytes(in, out); +} + +aoo_sample int16_to_sample(const char *in){ + convert c; +#if BYTE_ORDER == BIG_ENDIAN + memcpy(c.b, in, 2); // optimized away +#else + c.b[0] = in[1]; + c.b[1] = in[0]; +#endif + return(aoo_sample)c.i16 / 32768.f; +} + +aoo_sample int24_to_sample(const char *in) +{ + convert c; + // copy to the highest 3 bytes! +#if BYTE_ORDER == BIG_ENDIAN + c.b[0] = in[0]; + c.b[1] = in[1]; + c.b[2] = in[2]; + c.b[3] = 0; +#else + c.b[0] = 0; + c.b[1] = in[2]; + c.b[2] = in[1]; + c.b[3] = in[0]; +#endif + return (aoo_sample)c.i32 / 0x7fffffff; +} + +aoo_sample float32_to_sample(const char *in) +{ + return aoo::from_bytes(in); +} + +aoo_sample float64_to_sample(const char *in) +{ + return aoo::from_bytes(in); +} + +void print_settings(const aoo_format_pcm& f) +{ + LOG_VERBOSE("PCM settings: " + << "nchannels = " << f.header.nchannels + << ", blocksize = " << f.header.blocksize + << ", samplerate = " << f.header.samplerate + << ", bitdepth = " << bytes_per_sample(f.bitdepth)); +} + +/*//////////////////// codec //////////////////////////*/ + +struct codec { + codec(){ + memset(&format, 0, sizeof(aoo_format_pcm)); + } + aoo_format_pcm format; +}; + +void validate_format(aoo_format_pcm& f, bool loud = true) +{ + f.header.codec = AOO_CODEC_PCM; // static string! + f.header.size = sizeof(aoo_format_pcm); // actual size! + + // validate blocksize + if (f.header.blocksize <= 0){ + if (loud){ + LOG_WARNING("PCM: bad blocksize " << f.header.blocksize + << ", using 64 samples"); + } + f.header.blocksize = 64; + } + // validate samplerate + if (f.header.samplerate <= 0){ + if (loud){ + LOG_WARNING("PCM: bad samplerate " << f.header.samplerate + << ", using 44100"); + } + f.header.samplerate = 44100; + } + // validate channels + if (f.header.nchannels <= 0 || f.header.nchannels > 255){ + if (loud){ + LOG_WARNING("PCM: bad channel count " << f.header.nchannels + << ", using 1 channel"); + } + f.header.nchannels = 1; + } + // validate bitdepth + if (f.bitdepth < 0 || f.bitdepth > AOO_PCM_BITDEPTH_SIZE){ + if (loud){ + LOG_WARNING("PCM: bad bitdepth, using 32bit float"); + } + f.bitdepth = AOO_PCM_FLOAT32; + } +} + +aoo_error compare(codec *c, const aoo_format_pcm *fmt) +{ + // copy and validate! + aoo_format_pcm f1; + memcpy(&f1, fmt, sizeof(aoo_format_pcm)); + + auto& f2 = c->format; + auto& h1 = f1.header; + auto& h2 = f2.header; + + // check before validate()! + if (strcmp(h1.codec, h2.codec) || + h1.size != h2.size) { + return false; + } + + validate_format(f1, false); + + return h1.blocksize == h2.blocksize && + h1.samplerate == h2.samplerate && + h1.nchannels == h2.nchannels && + f1.bitdepth == f2.bitdepth; +} + +aoo_error set_format(codec *c, aoo_format_pcm *fmt) +{ + if (strcmp(fmt->header.codec, AOO_CODEC_PCM)){ + return AOO_ERROR_UNSPECIFIED; + } + if (fmt->header.size < sizeof(aoo_format_pcm)){ + return AOO_ERROR_UNSPECIFIED; + } + + validate_format(*fmt); + + // save and print settings + memcpy(&c->format, fmt, sizeof(aoo_format_pcm)); + print_settings(c->format); + + return AOO_OK; +} + +aoo_error get_format(codec *c, aoo_format *f, size_t size) +{ + // check if format has been set + if (c->format.header.codec){ + if (size >= c->format.header.size){ + memcpy(f, &c->format, sizeof(aoo_format_pcm)); + return AOO_OK; + } else { + return AOO_ERROR_UNSPECIFIED; + } + } else { + return AOO_ERROR_UNSPECIFIED; + } +} + +aoo_error pcm_ctl(void *x, int32_t ctl, void *ptr, int32_t size){ + switch (ctl){ + case AOO_CODEC_SET_FORMAT: + assert(size >= sizeof(aoo_format)); + return set_format((codec *)x, (aoo_format_pcm *)ptr); + case AOO_CODEC_GET_FORMAT: + return get_format((codec *)x, (aoo_format *)ptr, size); + case AOO_CODEC_RESET: + // no op + return AOO_OK; + case AOO_CODEC_FORMAT_EQUAL: + assert(size >= sizeof(aoo_format)); + return compare((codec *)x, (aoo_format_pcm *)ptr); + default: + LOG_WARNING("PCM: unsupported codec ctl " << ctl); + return AOO_ERROR_UNSPECIFIED; + } +} + +void *encoder_new(){ + auto obj = g_allocator.alloc(sizeof(codec), g_allocator.context); + new (obj) codec {}; + return obj; +} + +void encoder_free(void *enc){ + static_cast(enc)->~codec(); + g_allocator.free(enc, sizeof(codec), g_allocator.context); +} + +aoo_error encode(void *enc, + const aoo_sample *s, int32_t n, + char *buf, int32_t *size) +{ + auto bitdepth = static_cast(enc)->format.bitdepth; + auto samplesize = bytes_per_sample(bitdepth); + auto nbytes = samplesize * n; + + if (*size < nbytes){ + LOG_WARNING("PCM: size mismatch! input bytes: " + << nbytes << ", output bytes " << *size); + return AOO_ERROR_UNSPECIFIED; + } + + auto samples_to_blob = [&](auto fn){ + auto b = buf; + for (int i = 0; i < n; ++i){ + fn(s[i], b); + b += samplesize; + } + }; + + switch (bitdepth){ + case AOO_PCM_INT16: + samples_to_blob(sample_to_int16); + break; + case AOO_PCM_INT24: + samples_to_blob(sample_to_int24); + break; + case AOO_PCM_FLOAT32: + samples_to_blob(sample_to_float32); + break; + case AOO_PCM_FLOAT64: + samples_to_blob(sample_to_float64); + break; + default: + // unknown bitdepth + break; + } + + *size = nbytes; + + return AOO_OK; +} + +void *decoder_new(){ + return new codec; +} + +void decoder_free(void *dec){ + delete (codec *)dec; +} + +aoo_error decode(void *dec, + const char *buf, int32_t size, + aoo_sample *s, int32_t *n) +{ + auto c = static_cast(dec); + assert(c->format.header.blocksize != 0); + + if (!buf){ + for (int i = 0; i < *n; ++i){ + s[i] = 0; + } + return AOO_OK; // dropped block + } + + auto samplesize = bytes_per_sample(c->format.bitdepth); + auto nsamples = size / samplesize; + + if (*n < nsamples){ + LOG_WARNING("PCM: size mismatch! input samples: " + << nsamples << ", output samples " << *n); + return AOO_ERROR_UNSPECIFIED; + } + + auto blob_to_samples = [&](auto convfn){ + auto b = buf; + for (int i = 0; i < *n; ++i, b += samplesize){ + s[i] = convfn(b); + } + }; + + switch (c->format.bitdepth){ + case AOO_PCM_INT16: + blob_to_samples(int16_to_sample); + break; + case AOO_PCM_INT24: + blob_to_samples(int24_to_sample); + break; + case AOO_PCM_FLOAT32: + blob_to_samples(float32_to_sample); + break; + case AOO_PCM_FLOAT64: + blob_to_samples(float64_to_sample); + break; + default: + // unknown bitdepth + return AOO_ERROR_UNSPECIFIED; + } + + *n = nsamples; + + return AOO_OK; +} + +aoo_error serialize(const aoo_format *f, char *buf, int32_t *size) +{ + if (*size >= 4){ + auto fmt = (const aoo_format_pcm *)f; + aoo::to_bytes(fmt->bitdepth, buf); + *size = 4; + + return AOO_OK; + } else { + LOG_ERROR("PCM: couldn't write settings - buffer too small!"); + return AOO_ERROR_UNSPECIFIED; + } +} + +aoo_error deserialize(const aoo_format *header, const char *buf, + int32_t nbytes, aoo_format *f, int32_t size) +{ + if (nbytes < 4){ + LOG_ERROR("PCM: couldn't read format - not enough data!"); + return AOO_ERROR_UNSPECIFIED; + } + if (size < sizeof(aoo_format_pcm)){ + LOG_ERROR("PCM: output format storage too small"); + return AOO_ERROR_UNSPECIFIED; + } + auto fmt = (aoo_format_pcm *)f; + // header + fmt->header.codec = AOO_CODEC_PCM; // static string! + fmt->header.size = sizeof(aoo_format_pcm); // actual size! + fmt->header.blocksize = header->blocksize; + fmt->header.nchannels = header->nchannels; + fmt->header.samplerate = header->samplerate; + // options + fmt->bitdepth = (aoo_pcm_bitdepth)aoo::from_bytes(buf); + + return AOO_OK; +} + +aoo_codec codec_class = { + AOO_CODEC_PCM, + // encoder + encoder_new, + encoder_free, + pcm_ctl, + encode, + // decoder + decoder_new, + decoder_free, + pcm_ctl, + decode, + // helper + serialize, + deserialize +}; + +} // namespace + +void aoo_codec_pcm_setup(aoo_codec_registerfn fn, const aoo_allocator *alloc){ + if (alloc){ + g_allocator = *alloc; + } + fn(AOO_CODEC_PCM, &codec_class); +} + diff --git a/deps/aoo/aoo/src/imp.hpp b/deps/aoo/aoo/src/imp.hpp new file mode 100644 index 00000000..414e3654 --- /dev/null +++ b/deps/aoo/aoo/src/imp.hpp @@ -0,0 +1,171 @@ +#pragma once + +#include "aoo/aoo.h" + +#include +#include +#include +#include + +namespace aoo { + +uint32_t make_version(); + +bool check_version(uint32_t version); + +char * copy_string(const char *s); + +void free_string(char *s); + +void * copy_sockaddr(const void *sa, int32_t len); + +void free_sockaddr(void *sa, int32_t len); + +namespace net { + +aoo_error parse_pattern(const char *msg, int32_t n, aoo_type& type, int32_t& offset); + +} // net + +/*///////////////////// allocator ////////////////////*/ + + +#if AOO_CUSTOM_ALLOCATOR || AOO_DEBUG_MEMORY + +void * allocate(size_t size); + +template +T * construct(U&&... args){ + auto ptr = allocate(sizeof(T)); + new (ptr) T(std::forward(args)...); + return (T *)ptr; +} + +void deallocate(void *ptr, size_t size); + +template +void destroy(T *x){ + x->~T(); + deallocate(x, sizeof(T)); +} + +template +class allocator { +public: + using value_type = T; + + allocator() noexcept = default; + + template + allocator(const allocator&) noexcept {} + + template + allocator& operator=(const allocator&) noexcept {} + + template + struct rebind { + typedef allocator other; + }; + + value_type* allocate(size_t n) { + return (value_type *)aoo::allocate(sizeof(T) * n); + } + + void deallocate(value_type* p, size_t n) noexcept { + aoo::deallocate(p, sizeof(T) * n); + } +}; + +template +bool operator==(allocator const&, allocator const&) noexcept +{ + return true; +} + +template +bool operator!=(allocator const& x, allocator const& y) noexcept +{ + return !(x == y); +} + +#else + +inline void * allocate(size_t size){ + return operator new(size); +} + +template +T * construct(U&&... args){ + return new T(std::forward(args)...); +} + +inline void deallocate(void *ptr, size_t size){ + operator delete(ptr); +} + +template +void destroy(T *x){ + delete x; +} + +template +using allocator = std::allocator; + +#endif + +/*////////////// memory //////////////*/ + +struct memory_block { + struct { + memory_block *next; + size_t size; + } header; + char mem[1]; + + static memory_block * allocate(size_t size); + + static void free(memory_block *mem); + + static memory_block * from_bytes(void *bytes){ + return (memory_block *)((char *)bytes - sizeof(memory_block::header)); + } + + size_t full_size() const { + return header.size + sizeof(header); + } + + size_t size() const { + return header.size; + } + + void * data() { + return mem; + } +}; + +class memory_list { +public: + memory_list() = default; + ~memory_list(); + memory_list(memory_list&& other) + : memlist_(other.memlist_.exchange(nullptr)){} + memory_list& operator=(memory_list&& other){ + memlist_.store(other.memlist_.exchange(nullptr)); + return *this; + } + memory_block* alloc(size_t size); + void free(memory_block* b); +private: + std::atomic memlist_{nullptr}; +}; + +/*///////////////// misc ///////////////////*/ + +struct format_deleter { + void operator() (void *x) const { + auto f = static_cast(x); + aoo::deallocate(x, f->size); + } +}; + +} // aoo diff --git a/deps/aoo/lib/src/SLIP.hpp b/deps/aoo/aoo/src/net/SLIP.hpp similarity index 87% rename from deps/aoo/lib/src/SLIP.hpp rename to deps/aoo/aoo/src/net/SLIP.hpp index fe4e45a1..02332f21 100644 --- a/deps/aoo/lib/src/SLIP.hpp +++ b/deps/aoo/aoo/src/net/SLIP.hpp @@ -5,8 +5,12 @@ namespace aoo { +template> class SLIP { public: + SLIP(const Alloc& alloc = Alloc {}) + : buffer_(alloc) {} + static const uint8_t END = 192; static const uint8_t ESC = 219; static const uint8_t ESC_END = 220; @@ -25,24 +29,27 @@ class SLIP { bool write_packet(const uint8_t *data, int32_t size); private: - std::vector buffer_; + std::vector buffer_; int32_t rdhead_ = 0; int32_t wrhead_ = 0; int32_t balance_ = 0; }; -inline void SLIP::setup(int32_t buffersize){ +template +inline void SLIP::setup(int32_t buffersize){ buffer_.resize(buffersize); reset(); } -inline void SLIP::reset(){ +template +inline void SLIP::reset(){ rdhead_ = 0; wrhead_ = 0; balance_ = 0; } -inline int32_t SLIP::read_bytes(uint8_t *buffer, int32_t size){ +template +inline int32_t SLIP::read_bytes(uint8_t *buffer, int32_t size){ auto capacity = (int32_t)buffer_.size(); if (size > balance_){ size = balance_; @@ -66,7 +73,8 @@ inline int32_t SLIP::read_bytes(uint8_t *buffer, int32_t size){ return size; } -inline int32_t SLIP::write_bytes(const uint8_t *data, int32_t size){ +template +inline int32_t SLIP::write_bytes(const uint8_t *data, int32_t size){ auto capacity = (int32_t)buffer_.size(); auto space = capacity - balance_; if (size > space){ @@ -89,7 +97,8 @@ inline int32_t SLIP::write_bytes(const uint8_t *data, int32_t size){ return size; } -inline int32_t SLIP::read_packet(uint8_t *buffer, int32_t size){ +template +inline int32_t SLIP::read_packet(uint8_t *buffer, int32_t size){ int32_t nbytes = 0; int32_t head = rdhead_; @@ -159,7 +168,8 @@ inline int32_t SLIP::read_packet(uint8_t *buffer, int32_t size){ return packetsize; } -inline bool SLIP::write_packet(const uint8_t *data, int32_t size){ +template +inline bool SLIP::write_packet(const uint8_t *data, int32_t size){ int32_t available = buffer_.size() - balance_; int32_t nbytes = 0; int32_t head = wrhead_; diff --git a/deps/aoo/aoo/src/net/client.cpp b/deps/aoo/aoo/src/net/client.cpp new file mode 100644 index 00000000..9a885c4d --- /dev/null +++ b/deps/aoo/aoo/src/net/client.cpp @@ -0,0 +1,1993 @@ +/* Copyright (c) 2010-Now Christof Ressi, Winfried Ritsch and others. + * For information on usage and redistribution, and for a DISCLAIMER OF ALL + * WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ + +#include "client.hpp" + +#include +#include +#include +#include + +#include "md5/md5.h" + +#ifndef _WIN32 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#define AOO_NET_MSG_SERVER_PING \ + AOO_MSG_DOMAIN AOO_NET_MSG_SERVER AOO_NET_MSG_PING + +#define AOO_NET_MSG_PEER_PING \ + AOO_MSG_DOMAIN AOO_NET_MSG_PEER AOO_NET_MSG_PING + +#define AOO_NET_MSG_PEER_REPLY \ + AOO_MSG_DOMAIN AOO_NET_MSG_PEER AOO_NET_MSG_REPLY + +#define AOO_NET_MSG_PEER_MESSAGE \ + AOO_MSG_DOMAIN AOO_NET_MSG_PEER AOO_NET_MSG_MESSAGE + +#define AOO_NET_MSG_SERVER_LOGIN \ + AOO_MSG_DOMAIN AOO_NET_MSG_SERVER AOO_NET_MSG_LOGIN + +#define AOO_NET_MSG_SERVER_REQUEST \ + AOO_MSG_DOMAIN AOO_NET_MSG_SERVER AOO_NET_MSG_REQUEST + +#define AOO_NET_MSG_SERVER_GROUP_JOIN \ + AOO_MSG_DOMAIN AOO_NET_MSG_SERVER AOO_NET_MSG_GROUP AOO_NET_MSG_JOIN + +#define AOO_NET_MSG_SERVER_GROUP_LEAVE \ + AOO_MSG_DOMAIN AOO_NET_MSG_SERVER AOO_NET_MSG_GROUP AOO_NET_MSG_LEAVE + +#define AOO_NET_MSG_GROUP_JOIN \ + AOO_NET_MSG_GROUP AOO_NET_MSG_JOIN + +#define AOO_NET_MSG_GROUP_LEAVE \ + AOO_NET_MSG_GROUP AOO_NET_MSG_LEAVE + +#define AOO_NET_MSG_PEER_JOIN \ + AOO_NET_MSG_PEER AOO_NET_MSG_JOIN + +#define AOO_NET_MSG_PEER_LEAVE \ + AOO_NET_MSG_PEER AOO_NET_MSG_LEAVE + +// debugging + +#define FORCE_RELAY 0 + +#define DEBUG_RELAY 0 + +namespace aoo { + +namespace net { + +std::string encrypt(const std::string& input){ + uint8_t result[16]; + MD5_CTX ctx; + MD5_Init(&ctx); + MD5_Update(&ctx, (uint8_t *)input.data(), input.size()); + MD5_Final(result, &ctx); + + char output[33]; + for (int i = 0; i < 16; ++i){ + sprintf(&output[i * 2], "%02X", result[i]); + } + + return output; +} + +void copy_string(const std::string& src, char *dst, int32_t size){ + const auto limit = size - 1; // leave space for '\0' + auto n = (src.size() > limit) ? limit : src.size(); + memcpy(dst, src.data(), n); + dst[n] = '\0'; +} + +/*//////////////////// OSC ////////////////////////////*/ + +// optimized version of aoo_parse_pattern() for client/server +aoo_error parse_pattern(const char *msg, int32_t n, aoo_type& type, int32_t& offset) +{ + int32_t count = 0; + if (n >= AOO_BIN_MSG_HEADER_SIZE && + !memcmp(msg, AOO_BIN_MSG_DOMAIN, AOO_BIN_MSG_DOMAIN_SIZE)) + { + // domain (int32), type (int16), cmd (int16), id (int32) ... + type = aoo::from_bytes(msg + 4); + offset = 12; + + return AOO_OK; + } else if (n >= AOO_MSG_DOMAIN_LEN + && !memcmp(msg, AOO_MSG_DOMAIN, AOO_MSG_DOMAIN_LEN)) + { + count += AOO_MSG_DOMAIN_LEN; + if (n >= (count + AOO_NET_MSG_SERVER_LEN) + && !memcmp(msg + count, AOO_NET_MSG_SERVER, AOO_NET_MSG_SERVER_LEN)) + { + type = AOO_TYPE_SERVER; + count += AOO_NET_MSG_SERVER_LEN; + } + else if (n >= (count + AOO_NET_MSG_CLIENT_LEN) + && !memcmp(msg + count, AOO_NET_MSG_CLIENT, AOO_NET_MSG_CLIENT_LEN)) + { + type = AOO_TYPE_CLIENT; + count += AOO_NET_MSG_CLIENT_LEN; + } + else if (n >= (count + AOO_NET_MSG_PEER_LEN) + && !memcmp(msg + count, AOO_NET_MSG_PEER, AOO_NET_MSG_PEER_LEN)) + { + type = AOO_TYPE_PEER; + count += AOO_NET_MSG_PEER_LEN; + } + else if (n >= (count + AOO_NET_MSG_RELAY_LEN) + && !memcmp(msg + count, AOO_NET_MSG_RELAY, AOO_NET_MSG_RELAY_LEN)) + { + type = AOO_TYPE_RELAY; + count += AOO_NET_MSG_RELAY_LEN; + } else { + return AOO_ERROR_UNSPECIFIED; + } + + offset = count; + + return AOO_OK; + } else { + return AOO_ERROR_UNSPECIFIED; // not an AoO message + } +} + +} // net +} // aoo + +/*//////////////////// AoO client /////////////////////*/ + +aoo_net_client * aoo_net_client_new(const void *address, int32_t addrlen, + uint32_t flags) { + return aoo::construct(address, addrlen, flags); +} + +aoo::net::client_imp::client_imp(const void *address, int32_t addrlen, uint32_t flags) +{ + ip_address addr((const sockaddr *)address, addrlen); + udp_client_ = std::make_unique(*this, addr.port(), flags); + type_ = addr.type(); + + eventsocket_ = socket_udp(0); + if (eventsocket_ < 0){ + // TODO handle error + socket_error_print("socket_udp"); + } + + sendbuffer_.setup(65536); + recvbuffer_.setup(65536); + + // commands_.reserve(256); + // messages_.reserve(256); + // events_.reserve(256); +} + +void aoo_net_client_free(aoo_net_client *client){ + // cast to correct type because base class + // has no virtual destructor! + aoo::destroy(static_cast(client)); +} + +aoo::net::client_imp::~client_imp() { + if (socket_ >= 0){ + socket_close(socket_); + } +} + +aoo_error aoo_net_client_run(aoo_net_client *client){ + return client->run(); +} + +aoo_error aoo::net::client_imp::run(){ + start_time_ = time_tag::now(); + + while (!quit_.load()){ + double timeout = 0; + + time_tag now = time_tag::now(); + auto elapsed_time = time_tag::duration(start_time_, now); + + if (state_.load() == client_state::connected){ + auto delta = elapsed_time - last_ping_time_; + auto interval = ping_interval(); + if (delta >= interval){ + // send ping + if (socket_ >= 0){ + char buf[64]; + osc::OutboundPacketStream msg(buf, sizeof(buf)); + msg << osc::BeginMessage(AOO_NET_MSG_SERVER_PING) + << osc::EndMessage; + + send_server_message(msg.Data(), msg.Size()); + } else { + LOG_ERROR("aoo_client: bug send_ping()"); + } + + last_ping_time_ = elapsed_time; + timeout = interval; + } else { + timeout = interval - delta; + } + } else { + timeout = -1; + } + + if (!wait_for_event(timeout)){ + break; + } + + // handle commands + std::unique_ptr cmd; + while (commands_.try_pop(cmd)){ + cmd->perform(*this); + } + + // handle messages + std::unique_ptr msg; + while (tcp_messages_.try_pop(msg)){ + // call with dummy reply function + msg->perform(*this, sendfn{}); + } + + if (!peers_.try_free()){ + LOG_DEBUG("aoo::client: try_free() would block"); + } + } + return AOO_OK; +} + +aoo_error aoo_net_client_quit(aoo_net_client *client){ + return client->quit(); +} + +aoo_error aoo::net::client_imp::quit(){ + quit_.store(true); + if (!signal()){ + // force wakeup by closing the socket. + // this is not nice and probably undefined behavior, + // the MSDN docs explicitly forbid it! + socket_close(eventsocket_); + } + return AOO_OK; +} + +aoo_error aoo_net_client_add_source(aoo_net_client *client, + aoo_source *src, aoo_id id) +{ + return client->add_source(src, id); +} + +aoo_error aoo::net::client_imp::add_source(source *src, aoo_id id) +{ +#if 1 + for (auto& s : sources_){ + if (s.source == src){ + LOG_ERROR("aoo_client: source already added"); + return AOO_ERROR_UNSPECIFIED; + } else if (s.id == id){ + LOG_WARNING("aoo_client: source with id " << id + << " already added!"); + return AOO_ERROR_UNSPECIFIED; + } + } +#endif + sources_.push_back({ src, id }); + return AOO_OK; +} + +aoo_error aoo_net_client_remove_source(aoo_net_client *client, + aoo_source *src) +{ + return client->remove_source(src); +} + +aoo_error aoo::net::client_imp::remove_source(source *src) +{ + for (auto it = sources_.begin(); it != sources_.end(); ++it){ + if (it->source == src){ + sources_.erase(it); + return AOO_OK; + } + } + LOG_ERROR("aoo_client: source not found"); + return AOO_ERROR_UNSPECIFIED; +} + +aoo_error aoo_net_client_add_sink(aoo_net_client *client, + aoo_sink *sink, aoo_id id) +{ + return client->add_sink(sink, id); +} + +aoo_error aoo::net::client_imp::add_sink(sink *sink, aoo_id id) +{ +#if 1 + for (auto& s : sinks_){ + if (s.sink == sink){ + LOG_ERROR("aoo_client: sink already added"); + return AOO_OK; + } else if (s.id == id){ + LOG_WARNING("aoo_client: sink with id " << id + << " already added!"); + return AOO_ERROR_UNSPECIFIED; + } + } +#endif + sinks_.push_back({ sink, id }); + return AOO_OK; +} + +aoo_error aoo_net_client_remove_sink(aoo_net_client *client, + aoo_sink *sink) +{ + return client->remove_sink(sink); +} + +aoo_error aoo::net::client_imp::remove_sink(sink *sink) +{ + for (auto it = sinks_.begin(); it != sinks_.end(); ++it){ + if (it->sink == sink){ + sinks_.erase(it); + return AOO_OK; + } + } + LOG_ERROR("aoo_client: sink not found"); + return AOO_ERROR_UNSPECIFIED; +} + +aoo_error aoo_net_client_get_peer_address(aoo_net_client *client, + const char *group, const char *user, + void *address, int32_t *addrlen, uint32_t *flags) +{ + return client->get_peer_address(group, user, address, addrlen, flags); +} + +aoo_error aoo::net::client_imp::get_peer_address(const char *group, const char *user, + void *address, int32_t *addrlen, uint32_t *flags) +{ + peer_lock lock(peers_); + for (auto& p : peers_){ + // we can only access the address if the peer is connected! + if (p.match(group, user) && p.connected()){ + if (address){ + auto& addr = p.address(); + if (*addrlen < addr.length()){ + return AOO_ERROR_UNSPECIFIED; + } + memcpy(address, addr.address(), addr.length()); + *addrlen = addr.length(); + } + if (flags){ + *flags = p.flags(); + } + return AOO_OK; + } + } + return AOO_ERROR_UNSPECIFIED; +} + +aoo_error aoo_net_client_get_peer_info(aoo_net_client *client, + const void *address, int32_t addrlen, + aoo_net_peer_info *info) +{ + return client->get_peer_info(address, addrlen, info); +} + +aoo_error aoo::net::client_imp::get_peer_info(const void *address, int32_t addrlen, + aoo_net_peer_info *info) +{ + peer_lock lock(peers_); + for (auto& p : peers_){ + ip_address addr((const sockaddr *)address, addrlen); + if (p.match(addr)){ + aoo::net::copy_string(p.group(), info->group_name, sizeof(info->group_name)); + aoo::net::copy_string(p.user(), info->user_name, sizeof(info->user_name)); + info->user_id = p.id(); + info->flags = p.flags(); + + return AOO_OK; + } + } + return AOO_ERROR_UNSPECIFIED; +} + +aoo_error aoo_net_client_request(aoo_net_client *client, + aoo_net_request_type request, void *data, + aoo_net_callback callback, void *user) { + return client->send_request(request, data, callback, user); +} + +aoo_error aoo::net::client_imp::send_request(aoo_net_request_type request, void *data, + aoo_net_callback callback, void *user){ + switch (request){ + case AOO_NET_CONNECT_REQUEST: + { + auto d = (aoo_net_connect_request *)data; + do_connect(d->host, d->port, d->user_name, d->user_pwd, callback, user); + break; + } + case AOO_NET_DISCONNECT_REQUEST: + do_disconnect(callback, user); + break; + case AOO_NET_GROUP_JOIN_REQUEST: + { + auto d = (aoo_net_group_request *)data; + do_join_group(d->group_name, d->group_pwd, callback, user); + break; + } + case AOO_NET_GROUP_LEAVE_REQUEST: + { + auto d = (aoo_net_group_request *)data; + do_leave_group(d->group_name, callback, user); + break; + } + default: + LOG_ERROR("aoo client: unknown request " << request); + return AOO_ERROR_UNSPECIFIED; + } + return AOO_OK; +} + +aoo_error aoo_net_client_send_message(aoo_net_client *client, const char *data, int32_t size, + const void *addr, int32_t len, int32_t flags) +{ + return client->send_message(data, size, addr, len, flags); +} + +aoo_error aoo::net::client_imp::send_message(const char *data, int32_t size, + const void *addr, int32_t len, int32_t flags) +{ + // for now, we simply achieve 'reliable' messages by relaying over TCP + // LATER implement ack mechanism over UDP. + bool reliable = flags & AOO_NET_MESSAGE_RELIABLE; + + std::unique_ptr msg; + if (addr){ + if (len > 0){ + // peer message + msg = std::make_unique( + data, size, (const sockaddr *)addr, len, flags); + } else { + // group message + msg = std::make_unique( + data, size, (const char *)addr, flags); + } + } else { + msg = std::make_unique(data, size, flags); + } + + if (reliable){ + // add to TCP message queue + tcp_messages_.push(std::move(msg)); + signal(); + } else { + // add to UDP message queue + udp_messages_.push(std::move(msg)); + } + return AOO_OK; +} + +aoo_error aoo_net_client_handle_message(aoo_net_client *client, const char *data, + int32_t n, const void *addr, int32_t len) +{ + return client->handle_message(data, n, addr, len); +} + +aoo_error aoo::net::client_imp::handle_message(const char *data, int32_t n, + const void *addr, int32_t len){ + int32_t type; + aoo_id id; + int32_t onset; + auto err = aoo_parse_pattern(data, n, &type, &id, &onset); + if (err != AOO_OK){ + LOG_VERBOSE("aoo_client: not an AOO NET message!"); + return AOO_ERROR_UNSPECIFIED; + } + + if (type == AOO_TYPE_SOURCE){ + // forward to matching source + for (auto& s : sources_){ + if (s.id == id){ + return s.source->handle_message(data, n, addr, len); + } + } + LOG_WARNING("aoo_client: handle_message(): source not found"); + } else if (type == AOO_TYPE_SINK){ + // forward to matching sink + for (auto& s : sinks_){ + if (s.id == id){ + return s.sink->handle_message(data, n, addr, len); + } + } + LOG_WARNING("aoo_client: handle_message(): sink not found"); + } else if (udp_client_){ + // forward to UDP client + ip_address address((const sockaddr *)addr, len); + return udp_client_->handle_message(data, n, address, type, onset); + } + + return AOO_ERROR_UNSPECIFIED; +} + +aoo_error aoo_net_client_send(aoo_net_client *client, aoo_sendfn fn, void *user){ + return client->send(fn, user); +} + +aoo_error aoo::net::client_imp::send(aoo_sendfn fn, void *user){ + struct proxy_data { + client_imp *self; + aoo_sendfn fn; + void *user; + }; + + auto proxy = [](void *x, const char *data, int32_t n, + const void *address, int32_t addrlen, uint32_t flags){ + auto y = static_cast(x); + + bool relay = flags & AOO_ENDPOINT_RELAY; + if (relay){ + // relay via server + ip_address addr((const sockaddr *)address, addrlen); + + sendfn reply(y->fn, y->user); + + y->self->udp().send_peer_message(data, n, addr, reply, true); + + return (int32_t)0; + } else { + // just forward + return y->fn(y->user, data, n, address, addrlen, flags); + } + }; + + proxy_data data {this, fn, user}; + + // send sources and sinks + for (auto& s : sources_){ + s.source->send(proxy, &data); + } + for (auto& s : sinks_){ + s.sink->send(proxy, &data); + } + // send server messages + if (state_.load() != client_state::disconnected){ + time_tag now = time_tag::now(); + sendfn reply(fn, user); + + if (udp_client_){ + udp_client_->update(reply, now); + } + + // send outgoing peer/group messages + std::unique_ptr msg; + while (udp_messages_.try_pop(msg)){ + msg->perform(*this, reply); + } + + // update peers + peer_lock lock(peers_); + for (auto& p : peers_){ + p.send(reply, now); + } + } + return AOO_OK; +} + +aoo_error aoo_net_client_set_eventhandler(aoo_net_client *sink, aoo_eventhandler fn, + void *user, int32_t mode) +{ + return sink->set_eventhandler(fn, user, mode); +} + +aoo_error aoo::net::client_imp::set_eventhandler(aoo_eventhandler fn, void *user, + int32_t mode) +{ + eventhandler_ = fn; + eventcontext_ = user; + eventmode_ = (aoo_event_mode)mode; + return AOO_OK; +} + +aoo_bool aoo_net_client_events_available(aoo_net_server *client){ + return client->events_available(); +} + +aoo_bool aoo::net::client_imp::events_available(){ + return !events_.empty(); +} + +aoo_error aoo_net_client_poll_events(aoo_net_client *client){ + return client->poll_events(); +} + +aoo_error aoo::net::client_imp::poll_events(){ + // always thread-safe + std::unique_ptr e; + while (events_.try_pop(e)){ + eventhandler_(eventcontext_, &e->event_, AOO_THREAD_UNKNOWN); + } + return AOO_OK; +} + +aoo_error aoo_net_client_ctl(aoo_net_client *client, int32_t ctl, + intptr_t index, void *p, size_t size) +{ + return client->control(ctl, index, p, size); +} + +aoo_error aoo::net::client_imp::control(int32_t ctl, intptr_t index, + void *ptr, size_t size) +{ + LOG_WARNING("aoo_client: unsupported control " << ctl); + return AOO_ERROR_UNSPECIFIED; +} + +namespace aoo { +namespace net { + +bool client_imp::handle_peer_message(const osc::ReceivedMessage& msg, int onset, + const ip_address& addr) +{ + bool success = false; + // NOTE: we have to loop over *all* peers because there can + // be more than 1 peer on a given IP endpoint, since a single + // user can join multiple groups. + // LATER make this more efficient, e.g. by associating IP endpoints + // with peers instead of having them all in a single list. + peer_lock lock(peers_); + for (auto& p : peers_){ + // forward to matching or unconnected peers! + if (p.match(addr, true)){ + p.handle_message(msg, onset, addr); + success = true; + } + } + return success; +} + +template +void client_imp::perform_send_message(const char *data, int32_t size, int32_t flags, + const sendfn& fn, T&& filter) +{ + bool reliable = flags & AOO_NET_MESSAGE_RELIABLE; + // embed inside an OSC message: + // /aoo/peer/msg' (b) + try { + char buf[AOO_MAXPACKETSIZE]; + osc::OutboundPacketStream msg(buf, sizeof(buf)); + msg << osc::BeginMessage(AOO_NET_MSG_PEER_MESSAGE) + << osc::Blob(data, size) << osc::EndMessage; + + peer_lock lock(peers_); + for (auto& peer : peers_){ + if (filter(peer)){ + auto& addr = peer.address(); + LOG_DEBUG("aoo_client: send message " << data + << " to " << addr.name() << ":" << addr.port()); + // Note: reliable messages are dispatched in the TCP network thread, + // unreliable messages are dispatched in the UDP network thread. + if (reliable){ + send_peer_message(msg.Data(), msg.Size(), addr); + } else if (udp_client_){ + udp_client_->send_peer_message(msg.Data(), msg.Size(), + addr, fn, peer.relay()); + } + } + } + } catch (const osc::Exception& e){ + LOG_ERROR("aoo_client: error sending OSC message: " << e.what()); + } +} + +void client_imp::do_connect(const char *host, int port, + const char *name, const char *pwd, + aoo_net_callback cb, void *user) +{ + auto cmd = std::make_unique(cb, user, host, port, + name, encrypt(pwd)); + + push_command(std::move(cmd)); +} + +void client_imp::perform_connect(const std::string& host, int port, + const std::string& name, const std::string& pwd, + aoo_net_callback cb, void *user) +{ + auto state = state_.load(); + if (state != client_state::disconnected){ + aoo_net_error_reply reply; + reply.error_code = 0; + if (state == client_state::connected){ + reply.error_message = "already connected"; + } else { + reply.error_message = "already connecting"; + } + + if (cb) cb(user, AOO_ERROR_UNSPECIFIED, &reply); + + return; + } + + state_.store(client_state::connecting); + + int err = try_connect(host, port); + if (err != 0){ + // event + std::string errmsg = socket_strerror(err); + + close(); + + aoo_net_error_reply reply; + reply.error_code = err; + reply.error_message = errmsg.c_str(); + + if (cb) cb(user, AOO_ERROR_UNSPECIFIED, &reply); + + return; + } + + username_ = name; + password_ = pwd; + callback_ = cb; + userdata_ = user; + + state_.store(client_state::handshake); +} + +int client_imp::try_connect(const std::string &host, int port){ + socket_ = socket_tcp(0); + if (socket_ < 0){ + int err = socket_errno(); + LOG_ERROR("aoo_client: couldn't create socket (" << err << ")"); + return err; + } + // resolve host name + auto result = ip_address::resolve(host, port, type_); + if (result.empty()){ + int err = socket_errno(); + // LATER think about best way for error handling. Maybe exception? + LOG_ERROR("aoo_client: couldn't resolve hostname (" << socket_errno() << ")"); + return err; + } + + LOG_DEBUG("aoo_client: server address list:"); + for (auto& addr : result){ + LOG_DEBUG("\t" << addr.name() << " " << addr.port()); + } + + // for actual TCP connection, just pick the first result + auto& remote = result.front(); + LOG_VERBOSE("try to connect to " << remote.name() << "/" << port); + + // try to connect (LATER make timeout configurable) + if (socket_connect(socket_, remote, 5) < 0){ + int err = socket_errno(); + LOG_ERROR("aoo_client: couldn't connect (" << err << ")"); + return err; + } + + // get local network interface + ip_address temp; + if (socket_address(socket_, temp) < 0){ + int err = socket_errno(); + LOG_ERROR("aoo_client: couldn't get socket name (" << err << ")"); + return err; + } + ip_address local(temp.name(), udp_client_->port(), type_); + + LOG_VERBOSE("aoo_client: successfully connected to " + << remote.name() << " on port " << remote.port()); + LOG_VERBOSE("aoo_client: local address: " << local.name()); + + udp_client_->start_handshake(local, std::move(result)); + + return 0; +} + +void client_imp::do_disconnect(aoo_net_callback cb, void *user){ + auto cmd = std::make_unique(cb, user); + + push_command(std::move(cmd)); +} + +void client_imp::perform_disconnect(aoo_net_callback cb, void *user){ + auto state = state_.load(); + if (state != client_state::connected){ + aoo_net_error_reply reply; + reply.error_message = (state == client_state::disconnected) + ? "not connected" : "still connecting"; + reply.error_code = 0; + + if (cb) cb(user, AOO_ERROR_UNSPECIFIED, &reply); + + return; + } + + close(true); + + if (cb) cb(user, AOO_OK, nullptr); // always succeeds +} + +void client_imp::perform_login(const ip_address_list& addrlist){ + state_.store(client_state::login); + + char buf[AOO_MAXPACKETSIZE]; + + osc::OutboundPacketStream msg(buf, sizeof(buf)); + msg << osc::BeginMessage(AOO_NET_MSG_SERVER_LOGIN) + << (int32_t)make_version() + << username_.c_str() << password_.c_str(); + for (auto& addr : addrlist){ + // unmap IPv4 addresses for IPv4 only servers + msg << addr.name_unmapped() << addr.port(); + } + msg << osc::EndMessage; + send_server_message(msg.Data(), msg.Size()); + + // send legacy one second, just in case we are talking to a legacy server + osc::OutboundPacketStream msg2(buf, sizeof(buf)); + msg2 << osc::BeginMessage(AOO_NET_MSG_SERVER_LOGIN) + << username_.c_str() << password_.c_str(); + for (auto& addr : addrlist){ + // unmap IPv4 addresses for IPv4 only servers + msg2 << addr.name_unmapped() << addr.port(); + } + msg2 << osc::EndMessage; + send_server_message(msg2.Data(), msg2.Size()); +} + +void client_imp::perform_timeout(){ + aoo_net_error_reply reply; + reply.error_code = 0; + reply.error_message = "UDP handshake time out"; + + callback_(userdata_, AOO_ERROR_UNSPECIFIED, &reply); + + if (state_.load() == client_state::handshake){ + close(); + } +} + +void client_imp::do_join_group(const char *group, const char *pwd, + aoo_net_callback cb, void *user){ + auto cmd = std::make_unique(cb, user, group, encrypt(pwd)); + + push_command(std::move(cmd)); +} + +void client_imp::perform_join_group(const std::string &group, const std::string &pwd, + aoo_net_callback cb, void *user) +{ + + auto request = [group, cb, user]( + const char *pattern, + const osc::ReceivedMessage& msg) + { + if (!strcmp(pattern, AOO_NET_MSG_GROUP_JOIN)){ + auto it = msg.ArgumentsBegin(); + std::string g = (it++)->AsString(); + if (g == group){ + int32_t status = (it++)->AsInt32(); + if (status > 0){ + LOG_VERBOSE("aoo_client: successfully joined group " << group); + if (cb) cb(user, AOO_OK, nullptr); + } else { + std::string errmsg; + if (msg.ArgumentCount() > 2){ + errmsg = (it++)->AsString(); + LOG_WARNING("aoo_client: couldn't join group " + << group << ": " << errmsg); + } else { + errmsg = "unknown error"; + } + // reply + aoo_net_error_reply reply; + reply.error_code = 0; + reply.error_message = errmsg.c_str(); + + if (cb) cb(user, AOO_ERROR_UNSPECIFIED, &reply); + } + + return true; + } + } + return false; + }; + pending_requests_.push_back(request); + + char buf[AOO_MAXPACKETSIZE]; + osc::OutboundPacketStream msg(buf, sizeof(buf)); + msg << osc::BeginMessage(AOO_NET_MSG_SERVER_GROUP_JOIN) + << group.c_str() << pwd.c_str() << osc::EndMessage; + + send_server_message(msg.Data(), msg.Size()); +} + +void client_imp::do_leave_group(const char *group, + aoo_net_callback cb, void *user){ + auto cmd = std::make_unique(cb, user, group); + + push_command(std::move(cmd)); +} + +void client_imp::perform_leave_group(const std::string &group, + aoo_net_callback cb, void *user) +{ + auto request = [this, group, cb, user]( + const char *pattern, + const osc::ReceivedMessage& msg) + { + if (!strcmp(pattern, AOO_NET_MSG_GROUP_LEAVE)){ + auto it = msg.ArgumentsBegin(); + std::string g = (it++)->AsString(); + if (g == group){ + int32_t status = (it++)->AsInt32(); + if (status > 0){ + LOG_VERBOSE("aoo_client: successfully left group " << group); + + // remove all peers from this group + peer_lock lock(peers_); + for (auto it = peers_.begin(); it != peers_.end(); ){ + if (it->match(group)){ + it = peers_.erase(it); + } else { + ++it; + } + } + lock.unlock(); + + if (cb) cb(user, AOO_OK, nullptr); + } else { + std::string errmsg; + if (msg.ArgumentCount() > 2){ + errmsg = (it++)->AsString(); + LOG_WARNING("aoo_client: couldn't leave group " + << group << ": " << errmsg); + } else { + errmsg = "unknown error"; + } + // reply + aoo_net_error_reply reply; + reply.error_code = 0; + reply.error_message = errmsg.c_str(); + + if (cb) cb(user, AOO_ERROR_UNSPECIFIED, &reply); + } + + return true; + } + } + return false; + }; + pending_requests_.push_back(request); + + char buf[AOO_MAXPACKETSIZE]; + osc::OutboundPacketStream msg(buf, sizeof(buf)); + msg << osc::BeginMessage(AOO_NET_MSG_SERVER_GROUP_LEAVE) + << group.c_str() << osc::EndMessage; + + send_server_message(msg.Data(), msg.Size()); +} + +void client_imp::send_event(std::unique_ptr e) +{ + switch (eventmode_){ + case AOO_EVENT_POLL: + events_.push(std::move(e)); + break; + case AOO_EVENT_CALLBACK: + // client only has network threads + eventhandler_(eventcontext_, &e->event_, AOO_THREAD_NETWORK); + break; + default: + break; + } +} + +void client_imp::push_command(std::unique_ptr&& cmd){ + commands_.push(std::move(cmd)); + + signal(); +} + +bool client_imp::wait_for_event(float timeout){ + // LOG_DEBUG("aoo_client: wait " << timeout << " seconds"); + + struct pollfd fds[2]; + fds[0].fd = eventsocket_; + fds[0].events = POLLIN; + fds[0].revents = 0; + fds[1].fd = socket_; + fds[1].events = POLLIN; + fds[1].revents = 0; + + // round up to 1 ms! -1: block indefinitely + // NOTE: macOS requires the negative timeout to exactly -1! +#ifdef _WIN32 + int result = WSAPoll(fds, 2, timeout < 0 ? -1 : timeout * 1000.0 + 0.5); +#else + int result = poll(fds, 2, timeout < 0 ? -1 : timeout * 1000.0 + 0.5); +#endif + if (result < 0){ + int err = socket_errno(); + if (err == EINTR){ + return true; // ? + } else { + LOG_ERROR("aoo_client: poll failed (" << err << ")"); + socket_error_print("poll"); + return false; + } + } + + // event socket + if (fds[0].revents){ + // read empty packet + char buf[64]; + recv(eventsocket_, buf, sizeof(buf), 0); + // LOG_DEBUG("aoo_client: got signalled"); + } + + // tcp socket + if (socket_ >= 0 && fds[1].revents){ + receive_data(); + } + + return true; +} + +void client_imp::receive_data(){ + char buffer[AOO_MAXPACKETSIZE]; + auto result = recv(socket_, buffer, sizeof(buffer), 0); + if (result > 0){ + recvbuffer_.write_bytes((uint8_t *)buffer, result); + + // handle packets + uint8_t buf[AOO_MAXPACKETSIZE]; + while (true){ + auto size = recvbuffer_.read_packet(buf, sizeof(buf)); + if (size > 0){ + try { + osc::ReceivedPacket packet((const char *)buf, size); + if (packet.IsBundle()){ + osc::ReceivedBundle bundle(packet); + handle_server_bundle(bundle); + } else { + handle_server_message(packet.Contents(), packet.Size()); + } + } catch (const osc::Exception& e){ + LOG_ERROR("aoo_client: exception in receive_data: " << e.what()); + on_exception("server TCP message", e); + } + } else { + break; + } + } + } else if (result == 0){ + // connection closed by the remote server + on_socket_error(0); + } else { + int err = socket_errno(); + LOG_ERROR("aoo_client: recv() failed (" << err << ")"); + on_socket_error(err); + } +} + +void client_imp::send_server_message(const char *data, int32_t size){ + if (sendbuffer_.write_packet((const uint8_t *)data, size)){ + while (sendbuffer_.read_available()){ + uint8_t buf[1024]; + int32_t total = sendbuffer_.read_bytes(buf, sizeof(buf)); + + int32_t nbytes = 0; + while (nbytes < total){ + auto res = ::send(socket_, (char *)buf + nbytes, total - nbytes, 0); + if (res >= 0){ + nbytes += res; + #if 0 + LOG_DEBUG("aoo_client: sent " << res << " bytes"); + #endif + } else { + auto err = socket_errno(); + + LOG_ERROR("aoo_client: send() failed (" << err << ")"); + on_socket_error(err); + return; + } + } + } + LOG_DEBUG("aoo_client: sent " << data << " to server"); + } else { + LOG_ERROR("aoo_client: couldn't send " << data << " to server"); + } +} + +void client_imp::send_peer_message(const char *data, int32_t size, + const ip_address& addr) { + // /aoo/relay + char buf[AOO_MAXPACKETSIZE]; + osc::OutboundPacketStream msg(buf, sizeof(buf)); + // send unmapped IP address in case peer is IPv4 only! + msg << osc::BeginMessage(AOO_MSG_DOMAIN AOO_NET_MSG_RELAY) + << addr.name_unmapped() << addr.port() << osc::Blob(data, size) + << osc::EndMessage; + + send_server_message(msg.Data(), msg.Size()); +} + +void client_imp::handle_server_message(const char *data, int32_t n){ + osc::ReceivedPacket packet(data, n); + osc::ReceivedMessage msg(packet); + + aoo_type type; + int32_t onset; + auto err = parse_pattern(data, n, type, onset); + if (err != AOO_OK){ + LOG_VERBOSE("aoo_client: not an AOO NET message!"); + return; + } + + try { + if (type == AOO_TYPE_CLIENT){ + // now compare subpattern + auto pattern = msg.AddressPattern() + onset; + LOG_DEBUG("aoo_client: got message " << pattern << " from server"); + + if (!strcmp(pattern, AOO_NET_MSG_PING)){ + LOG_DEBUG("aoo_client: got TCP ping from server"); + } else if (!strcmp(pattern, AOO_NET_MSG_PEER_JOIN)){ + handle_peer_add(msg); + } else if (!strcmp(pattern, AOO_NET_MSG_PEER_LEAVE)){ + handle_peer_remove(msg); + } else if (!strcmp(pattern, AOO_NET_MSG_LOGIN)){ + handle_login(msg); + } else { + // handle reply + for (auto it = pending_requests_.begin(); it != pending_requests_.end();){ + if ((*it)(pattern, msg)){ + it = pending_requests_.erase(it); + return; + } else { + ++it; + } + } + LOG_ERROR("aoo_client: couldn't handle reply " << pattern); + } + } else if (type == AOO_TYPE_RELAY){ + handle_relay_message(msg); + } else { + LOG_WARNING("aoo_client: got unsupported message " << msg.AddressPattern()); + } + } catch (const osc::Exception& e){ + LOG_ERROR("aoo_client: exception on handling " << msg.AddressPattern() + << " message: " << e.what()); + on_exception("server TCP message", e, msg.AddressPattern()); + } +} + +void client_imp::handle_server_bundle(const osc::ReceivedBundle &bundle){ + auto it = bundle.ElementsBegin(); + while (it != bundle.ElementsEnd()){ + if (it->IsBundle()){ + osc::ReceivedBundle b(*it); + handle_server_bundle(b); + } else { + handle_server_message(it->Contents(), it->Size()); + } + ++it; + } +} + +void client_imp::handle_login(const osc::ReceivedMessage& msg){ + // make sure that state hasn't changed + if (state_.load() == client_state::login){ + auto it = msg.ArgumentsBegin(); + int32_t status = (it++)->AsInt32(); + + if (status > 0){ + int32_t id = -1; + uint32_t flags = 0; + if (msg.ArgumentCount() > 2){ + id = (it++)->AsInt32(); + flags = (it != msg.ArgumentsEnd()) ? (uint32_t)(it++)->AsInt32() : 0; + } else { + // legacy client login only send status + errmsg + flags |= AOO_NET_SERVER_LEGACY; + } + // connected! + state_.store(client_state::connected); + LOG_VERBOSE("aoo_client: successfully logged in (user ID: " + << id << " )"); + // notify + aoo_net_connect_reply reply; + reply.user_id = id; + reply.server_flags = flags; + server_flags_ = flags; + + callback_(userdata_, AOO_OK, &reply); + } else { + std::string errmsg; + if (msg.ArgumentCount() > 1){ + errmsg = (it++)->AsString(); + } else { + errmsg = "unknown error"; + } + LOG_WARNING("aoo_client: login failed: " << errmsg); + + // cache callback and userdata + auto cb = callback_; + auto ud = userdata_; + + close(); + + // notify + aoo_net_error_reply reply; + reply.error_code = 0; + reply.error_message = errmsg.c_str(); + + cb(ud, AOO_ERROR_UNSPECIFIED, &reply); + } + } +} + +static void unwrap_message(const osc::ReceivedMessage& msg, + ip_address& addr, ip_address::ip_type type, const char * & retdata, std::size_t & retsize) +{ + auto it = msg.ArgumentsBegin(); + + auto ip = (it++)->AsString(); + auto port = (it++)->AsInt32(); + + addr = ip_address(ip, port, type); + + const void *blobData; + osc::osc_bundle_element_size_t blobSize; + (it++)->AsBlob(blobData, blobSize); + + retdata = (const char *)blobData; + retsize = blobSize; + // return osc::ReceivedPacket((const char *)blobData, blobSize); +} + +void client_imp::handle_relay_message(const osc::ReceivedMessage &msg){ + ip_address addr; + const char * retmsg = 0; + std::size_t retsize = 0; + unwrap_message(msg, addr, type(), retmsg, retsize); + + osc::ReceivedPacket packet (retmsg, retsize); + osc::ReceivedMessage relayMsg(packet); + + // for now, we only handle peer OSC messages + if (!strcmp(relayMsg.AddressPattern(), AOO_NET_MSG_PEER_MESSAGE)){ + // get embedded OSC message + const void *data; + osc::osc_bundle_element_size_t size; + relayMsg.ArgumentsBegin()->AsBlob(data, size); + + LOG_DEBUG("aoo_client: got relayed peer message " << (const char *)data + << " from " << addr.name() << ":" << addr.port()); + + auto e = std::make_unique( + (const char *)data, size, addr); + + send_event(std::move(e)); + } else { + LOG_WARNING("aoo_client: got unexpected relay message " << relayMsg.AddressPattern()); + } +} + +void client_imp::handle_peer_add(const osc::ReceivedMessage& msg){ + auto count = msg.ArgumentCount(); + auto it = msg.ArgumentsBegin(); + std::string group = (it++)->AsString(); + std::string user = (it++)->AsString(); + int32_t id = 0; + count -= 2; + int32_t flags = 0; + bool legacy = false; + + // LEGACY: older sonobus did not have this id field + if (msg.TypeTags()[2] == 'i') { + id = (it++)->AsInt32(); + count -= 1; + } + else { + legacy = true; + server_flags_ |= AOO_NET_SERVER_LEGACY; + } + + ip_address_list addrlist; + while (count >= 2){ + std::string ip = (it++)->AsString(); + int32_t port = (it++)->AsInt32(); + ip_address addr(ip, port, type_); + if (addr.valid()){ + addrlist.push_back(addr); + } + count -= 2; + } + + peer_lock lock(peers_); + // check if peer already exists (shouldn't happen) + for (auto& p: peers_){ + if (p.match(group, id) || (legacy && p.match(group, user))){ + LOG_ERROR("aoo_client: peer " << p << " already added"); + return; + } + } + peers_.emplace_front(*this, id, group, user, std::move(addrlist), flags); + + auto e = std::make_unique( + AOO_NET_PEER_HANDSHAKE_EVENT, + group.c_str(), user.c_str(), id); + send_event(std::move(e)); + + LOG_VERBOSE("aoo_client: new peer " << peers_.front()); +} + +void client_imp::handle_peer_remove(const osc::ReceivedMessage& msg){ + auto it = msg.ArgumentsBegin(); + std::string group = (it++)->AsString(); + std::string user = (it++)->AsString(); + int32_t id = msg.ArgumentCount() > 2 ? (it++)->AsInt32() : -1; + + peer_lock lock(peers_); + auto result = std::find_if(peers_.begin(), peers_.end(), + [&](auto& p){ return (id >= 0) ? p.match(group, id) : p.match(group,user); }); + if (result == peers_.end()){ + LOG_ERROR("aoo_client: couldn't remove " << group << "|" << user << "|" << id); + return; + } + + // only send event if we're connected, which means + // that an AOO_NET_PEER_JOIN_EVENT has been sent. + if (result->connected()){ + ip_address addr = result->address(); + uint32_t flags = result->flags(); + + auto e = std::make_unique( + AOO_NET_PEER_LEAVE_EVENT, addr, + group.c_str(), user.c_str(), id, flags); + send_event(std::move(e)); + } + + peers_.erase(result); + + LOG_VERBOSE("aoo_client: peer " << group << "|" << user << " left"); +} + +bool client_imp::signal(){ + // LOG_DEBUG("aoo_client signal"); + return socket_signal(eventsocket_); +} + +void client_imp::close(bool manual){ + if (socket_ >= 0){ + socket_close(socket_); + socket_ = -1; + LOG_VERBOSE("aoo_client: connection closed"); + } + + username_.clear(); + password_.clear(); + callback_ = nullptr; + userdata_ = nullptr; + + sendbuffer_.reset(); + recvbuffer_.reset(); + + // remove all peers + peer_lock lock(peers_); + peers_.clear(); + + // clear pending request + // LATER call them all with some dummy input + // to avoid memleak if clients pass heap + // allocated request data, which is supposed + // to be freed in the callback. + pending_requests_.clear(); + + if (!manual && state_.load() == client_state::connected){ + auto e = std::make_unique(AOO_NET_DISCONNECT_EVENT); + send_event(std::move(e)); + } + state_.store(client_state::disconnected); +} + +void client_imp::on_socket_error(int err){ + char msg[256]; + if (err != 0) { + socket_strerror(err, msg, sizeof(msg)); + } else { + snprintf(msg, sizeof(msg), "connection closed by server"); + } + auto e = std::make_unique(err, msg); + + send_event(std::move(e)); + + close(); +} + +void client_imp::on_exception(const char *what, const osc::Exception &err, + const char *pattern){ + char msg[256]; + if (pattern){ + snprintf(msg, sizeof(msg), "exception in %s (%s): %s", + what, pattern, err.what()); + } else { + snprintf(msg, sizeof(msg), "exception in %s: %s", + what, err.what()); + } + + auto e = std::make_unique(0, msg); + + send_event(std::move(e)); + + close(); +} + +/*///////////////////// events ////////////////////////*/ + +client_imp::error_event::error_event(int32_t code, const char *msg) +{ + error_event_.type = AOO_NET_ERROR_EVENT; + error_event_.error_code = code; + error_event_.error_message = aoo::copy_string(msg); +} + +client_imp::error_event::~error_event() +{ + free_string((char *)error_event_.error_message); +} + +client_imp::ping_event::ping_event(int32_t type, const ip_address& addr, + uint64_t tt1, uint64_t tt2, uint64_t tt3) +{ + ping_event_.type = type; + ping_event_.address = copy_sockaddr(addr.address(), addr.length()); + ping_event_.addrlen = addr.length(); + ping_event_.tt1 = tt1; + ping_event_.tt2 = tt2; + ping_event_.tt3 = tt3; +} + +client_imp::ping_event::~ping_event() +{ + free_sockaddr((void *)ping_event_.address, ping_event_.addrlen); +} + +client_imp::peer_event::peer_event(int32_t type, const ip_address& addr, + const char *group, const char *user, + int32_t id, uint32_t flags) +{ + peer_event_.type = type; + peer_event_.address = copy_sockaddr(addr.address(), addr.length()); + peer_event_.addrlen = addr.length(); + peer_event_.group_name = aoo::copy_string(group); + peer_event_.user_name = aoo::copy_string(user); + peer_event_.user_id = id; + peer_event_.flags = flags; +} + +client_imp::peer_event::peer_event(int32_t type, const char *group, + const char *user, int32_t id) +{ + peer_event_.type = type; + peer_event_.address = nullptr; + peer_event_.addrlen = 0; + peer_event_.group_name = aoo::copy_string(group); + peer_event_.user_name = aoo::copy_string(user); + peer_event_.user_id = id; + peer_event_.flags = 0; +} + + +client_imp::peer_event::~peer_event() +{ + free_string((char *)peer_event_.user_name); + free_string((char *)peer_event_.group_name); + free_sockaddr((void *)peer_event_.address, peer_event_.addrlen); +} + +client_imp::message_event::message_event(const char *data, int32_t size, + const ip_address& addr) +{ + message_event_.type = AOO_NET_PEER_MESSAGE_EVENT; + message_event_.address = copy_sockaddr(addr.address(), addr.length()); + message_event_.addrlen = addr.length(); + auto msg = (char *)aoo::allocate(size); + memcpy(msg, data, size); + message_event_.data = msg; + message_event_.size = size; +} + +client_imp::message_event::~message_event() +{ + aoo::deallocate((char *)message_event_.data, message_event_.size); + free_sockaddr((void *)message_event_.address, message_event_.addrlen); +} + +/*///////////////////// udp_client ////////////////////*/ + +void udp_client::update(const sendfn& reply, time_tag now){ + auto elapsed_time = client_->elapsed_time_since(now); + auto delta = elapsed_time - last_ping_time_; + + auto state = client_->current_state(); + + if (state == client_state::handshake){ + // check for time out + // "first_ping_time_" is guaranteed to be set to 0 + // before the state changes to "handshake" + auto start = first_ping_time_.load(); + if (start == 0){ + first_ping_time_.store(elapsed_time); + } else if ((elapsed_time - start) > client_->request_timeout()){ + // handshake has timed out! + auto cmd = std::make_unique(); + + client_->push_command(std::move(cmd)); + + return; + } + // send handshakes in fast succession + if (delta >= client_->request_interval()){ + char buf[64]; + osc::OutboundPacketStream msg(buf, sizeof(buf)); + msg << osc::BeginMessage(AOO_NET_MSG_SERVER_REQUEST) << osc::EndMessage; + + scoped_shared_lock lock(mutex_); + for (auto& addr : server_addrlist_){ + reply(msg.Data(), msg.Size(), addr, 0); + } + last_ping_time_ = elapsed_time; + } + } else if (state == client_state::connected){ + // send regular pings + if (delta >= client_->ping_interval()){ + char buf[64]; + osc::OutboundPacketStream msg(buf, sizeof(buf)); + msg << osc::BeginMessage(AOO_NET_MSG_SERVER_PING) + << osc::EndMessage; + + send_server_message(msg.Data(), msg.Size(), reply); + last_ping_time_ = elapsed_time; + } + } +} + +aoo_error udp_client::handle_message(const char *data, int32_t n, + const ip_address &addr, + aoo_type type, int32_t onset){ + try { + osc::ReceivedPacket packet(data, n); + osc::ReceivedMessage msg(packet); + + LOG_DEBUG("aoo_client: handle UDP message " << msg.AddressPattern() + << " from " << addr.name() << ":" << addr.port()); + + if (type == AOO_TYPE_PEER){ + // peer message + // + // NOTE: during the handshake process it is expected that + // we receive UDP messages which we have to ignore: + // a) pings from a peer which we haven't had the chance to add yet + // b) pings sent to alternative endpoint addresses + if (!client_->handle_peer_message(msg, onset, addr)){ + LOG_VERBOSE("aoo_client: ignoring UDP message " + << msg.AddressPattern() << " from endpoint " + << addr.name() << ":" << addr.port()); + } + } else if (type == AOO_TYPE_CLIENT){ + // server message + if (is_server_address(addr)){ + handle_server_message(msg, onset); + } else { + LOG_WARNING("aoo_client: got message from unknown server " << addr.name()); + } + } else if (type == AOO_TYPE_RELAY){ + handle_relay_message(msg); + } else { + LOG_WARNING("aoo_client: got unexpected message " << msg.AddressPattern()); + return AOO_ERROR_UNSPECIFIED; + } + + return AOO_OK; + } catch (const osc::Exception& e){ + LOG_ERROR("aoo_client: exception in handle_message: " << e.what()); + #if 0 + on_exception("UDP message", e); + #endif + return AOO_ERROR_UNSPECIFIED; + } +} + +void udp_client::handle_relay_message(const osc::ReceivedMessage &msg){ + ip_address addr; + const char * retmsg = 0; + std::size_t retsize = 0; + unwrap_message(msg, addr, client_->type(), retmsg, retsize); + +#if DEBUG_RELAY + LOG_DEBUG("aoo_client: got relayed message " << packet.Contents()); +#endif + + client_->handle_message(retmsg, retsize, + addr.address(), addr.length()); +} + +void udp_client::send_peer_message(const char *data, int32_t size, + const ip_address& addr, + const sendfn& fn, bool relay) +{ + if (relay){ + char buf[AOO_MAXPACKETSIZE]; + osc::OutboundPacketStream msg(buf, sizeof(buf)); + // send unmapped address in case the peer is IPv4 only! + msg << osc::BeginMessage(AOO_MSG_DOMAIN AOO_NET_MSG_RELAY) + << addr.name_unmapped() << addr.port() << osc::Blob(data, size) + << osc::EndMessage; + send_server_message(msg.Data(), msg.Size(), fn); + } else { + fn(data, size, addr, 0); + } +} + +void udp_client::start_handshake(const ip_address& local, + ip_address_list&& remote) +{ + scoped_lock lock(mutex_); // to be really safe + first_ping_time_ = 0; + local_address_ = local; + public_addrlist_.clear(); + server_addrlist_ = std::move(remote); +} + +void udp_client::send_server_message(const char *data, int32_t size, const sendfn& fn) +{ + scoped_shared_lock lock(mutex_); + if (!server_addrlist_.empty()){ + auto& remote = server_addrlist_.front(); + if (remote.valid()){ + fn(data, size, remote, 0); + } + } +} + +void udp_client::handle_server_message(const osc::ReceivedMessage& msg, int onset){ + auto pattern = msg.AddressPattern() + onset; + try { + if (!strcmp(pattern, AOO_NET_MSG_PING)){ + LOG_DEBUG("aoo_client: got UDP ping from server"); + } else if (!strcmp(pattern, AOO_NET_MSG_REPLY)){ + if (client_->current_state() == client_state::handshake){ + // retrieve public IP + port + auto it = msg.ArgumentsBegin(); + std::string ip = (it++)->AsString(); + int port = (it++)->AsInt32(); + + ip_address addr(ip, port, client_->type()); + + scoped_lock lock(mutex_); + for (auto& a : public_addrlist_){ + if (a == addr){ + LOG_DEBUG("aoo_client: public address " << addr.name() + << " already received"); + return; // already received + } + } + public_addrlist_.push_back(addr); + LOG_VERBOSE("aoo_client: got public address " + << addr.name() << " " << addr.port()); + + // check if we got all public addresses + // LATER improve this + if (public_addrlist_.size() == server_addrlist_.size()){ + // now we can try to login + ip_address_list addrlist; + addrlist.reserve(public_addrlist_.size() + 1); + + // local address first (for backwards compatibility with older versions) + addrlist.push_back(local_address_); + addrlist.insert(addrlist.end(), + public_addrlist_.begin(), public_addrlist_.end()); + + auto cmd = std::make_unique(std::move(addrlist)); + + client_->push_command(std::move(cmd)); + } + } + } else { + LOG_WARNING("aoo_client: received unexpected UDP message " + << pattern << " from server"); + } + } catch (const osc::Exception& e){ + LOG_ERROR("aoo_client: exception on handling " << pattern + << " message: " << e.what()); + #if 0 + on_exception("server UDP message", e, pattern); + #endif + } +} + +bool udp_client::is_server_address(const ip_address& addr){ + // server message + scoped_shared_lock lock(mutex_); + for (auto& remote : server_addrlist_){ + if (remote == addr){ + return true; + } + } + return false; +} + +/*///////////////////// peer //////////////////////////*/ + +peer::peer(client_imp& client, int32_t id, const std::string& group, + const std::string& user, ip_address_list&& addrlist, int32_t flags) + : client_(&client), id_(id), flags_(flags), group_(group), user_(user), + addresses_(std::move(addrlist)) +{ + start_time_ = time_tag::now(); + + LOG_DEBUG("create peer " << *this); +} + +peer::~peer(){ + LOG_DEBUG("destroy peer " << *this); +} + +bool peer::match(const ip_address& addr, bool unconnected) const { + if (connected()){ + return real_address_ == addr; + } else { + return unconnected; + } +} + +bool peer::match(const std::string& group) const { + return group_ == group; // immutable! +} + +bool peer::match(const std::string& group, const std::string& user) const { + return group_ == group && user_ == user; // immutable! +} + +bool peer::match(const std::string& group, int32_t id) +{ + return id_ == id && group_ == group; // immutable! +} + +std::ostream& operator << (std::ostream& os, const peer& p) +{ + os << p.group_ << "|" << p.user_ << "|" << p.id_; + return os; +} + +void peer::send(const sendfn& reply, time_tag now){ + auto elapsed_time = time_tag::duration(start_time_, now); + auto delta = elapsed_time - last_pingtime_; + + if (connected()){ + // send regular ping + if (delta >= client_->ping_interval()){ + char buf[64]; + osc::OutboundPacketStream msg(buf, sizeof(buf)); + msg << osc::BeginMessage(AOO_NET_MSG_PEER_PING) << osc::EndMessage; + + client_->udp().send_peer_message(msg.Data(), msg.Size(), + real_address_, reply, relay()); + + last_pingtime_ = elapsed_time; + } + // reply to /ping message + if (got_ping_.exchange(false)){ + char buf[64]; + osc::OutboundPacketStream msg(buf, sizeof(buf)); + msg << osc::BeginMessage(AOO_NET_MSG_PEER_REPLY) << osc::EndMessage; + + client_->udp().send_peer_message(msg.Data(), msg.Size(), + real_address_, reply, relay()); + } + } else if (!timeout_) { + // try to establish UDP connection with peer + if (elapsed_time > client_->request_timeout()){ + // time out + bool can_relay = client_->have_server_flag(AOO_NET_SERVER_RELAY); + if (can_relay && !relay()){ + LOG_VERBOSE("aoo_client: try to relay " << *this); + // try to relay traffic over server + start_time_ = now; // reset timer + last_pingtime_ = 0; + flags_ |= AOO_ENDPOINT_RELAY; + flags_ &= ~(AOO_ENDPOINT_LEGACY); + return; + } + + // couldn't establish relay connection! + const char *what = relay() ? "relay" : "peer-to-peer"; + LOG_ERROR("aoo_client: couldn't establish UDP " << what + << " connection to " << *this << "; timed out after " + << client_->request_timeout() << " seconds"); + + std::stringstream ss; + ss << "couldn't establish connection with peer " << *this; + + auto e1 = std::make_unique(0, ss.str().c_str()); + client_->send_event(std::move(e1)); + + auto e2 = std::make_unique( + AOO_NET_PEER_TIMEOUT_EVENT, + group().c_str(), user().c_str(), id()); + client_->send_event(std::move(e2)); + + timeout_ = true; + + return; + } + // send handshakes in fast succession to all addresses + // until we get a reply from one of them (see handle_message()) + if (delta >= client_->request_interval()){ + char buf[64]; + char buf2[68]; + osc::OutboundPacketStream msg(buf, sizeof(buf)); + osc::OutboundPacketStream msg2(buf2, sizeof(buf2)); + + // Include user ID, so peers can identify us even + // if we're behind a symmetric NAT. + // NOTE: This trick doesn't work if both parties are + // behind a symmetrict NAT; in that case, UDP hole punching + // simply doesn't work. + + + + msg << osc::BeginMessage(AOO_NET_MSG_PEER_PING) + << (int32_t)id_ << osc::EndMessage; + + // legacy sonobus used 64 bit token field, so for backwards compat, send this too + msg2 << osc::BeginMessage(AOO_NET_MSG_PEER_PING) + << (int64_t)id_ << osc::EndMessage; + + for (auto& addr : addresses_){ + LOG_DEBUG("aoo_client: send handshake peer ping to: " << *this << " = " << addr.name() << ":" << addr.port()); + client_->udp().send_peer_message(msg.Data(), msg.Size(), + addr, reply, relay()); + + client_->udp().send_peer_message(msg2.Data(), msg2.Size(), + addr, reply, relay()); + } + + // LOG_DEBUG("send ping to " << *this); + + last_pingtime_ = elapsed_time; + } + } +} + +bool peer::handle_first_message(const osc::ReceivedMessage &msg, int onset, + const ip_address &addr) +{ +#if FORCE_RELAY + // force relay + if (!relay()){ + return false; + } +#endif + // Try to find matching address + for (auto& a : addresses_){ + if (a == addr){ + real_address_ = addr; + connected_.store(true, std::memory_order_release); + return true; + } + } + + // We might get a message from a peer behind a symmetric NAT. + // To be sure, check user ID, but only if provided + // (for backwards compatibility with older AOO clients) + auto pattern = msg.AddressPattern() + onset; + if (!strcmp(pattern, AOO_NET_MSG_PING)){ + if (msg.ArgumentCount() > 0 && msg.TypeTags()[0] == 'i'){ + try { + auto it = msg.ArgumentsBegin(); + int32_t id = it->AsInt32(); + if (id == id_){ + real_address_ = addr; + connected_.store(true); + LOG_WARNING("aoo_client: peer " << *this + << " is located behind a symmetric NAT!"); + return true; + } + } catch (const osc::Exception& e){ + LOG_ERROR("aoo_client: got bad " << pattern + << " message from peer: " << e.what()); + } + } + else if (msg.ArgumentCount() > 0 && msg.TypeTags()[0] == 'h'){ + // LEGACY: old sonobus pings use a 64bit "token" here + LOG_VERBOSE("aoo_client: legacy ping detected from peer " << *this); + flags_ |= AOO_ENDPOINT_LEGACY; + } else { + // ignore silently! + } + } else { + LOG_ERROR("aoo_client: got " << pattern + << " message from unknown peer"); + } + return false; +} + +void peer::handle_message(const osc::ReceivedMessage &msg, int onset, + const ip_address& addr) +{ + auto pattern = msg.AddressPattern() + onset; + + if (!connected()){ + if (!handle_first_message(msg, onset, addr)){ + return; + } + + if (!strcmp(pattern, AOO_NET_MSG_PING)){ + if (msg.ArgumentCount() > 0 && msg.TypeTags()[0] == 'h'){ + // LEGACY: old sonobus pings use a 64bit "token" here + LOG_VERBOSE("aoo_client: legacy ping detected from peer " << *this); + flags_ |= AOO_ENDPOINT_LEGACY; + } + } + + // push event + auto e = std::make_unique( + AOO_NET_PEER_JOIN_EVENT, addr, + group().c_str(), user().c_str(), id(), flags()); + + client_->send_event(std::move(e)); + + LOG_VERBOSE("aoo_client: successfully established connection with " + << *this << " [" << addr.name() << "]:" << addr.port() + << (relay() ? " (relayed)" : "")); + } + + try { + if (!strcmp(pattern, AOO_NET_MSG_PING)){ + LOG_DEBUG("aoo_client: got ping from " << *this); + got_ping_.store(true); + } else if (!strcmp(pattern, AOO_NET_MSG_REPLY)){ + LOG_DEBUG("aoo_client: got reply from " << *this); + } else if (!strcmp(pattern, AOO_NET_MSG_MESSAGE)){ + // get embedded OSC message + const void *data; + osc::osc_bundle_element_size_t size; + msg.ArgumentsBegin()->AsBlob(data, size); + + LOG_DEBUG("aoo_client: got message " << (const char *)data + << " from " << addr.name() << ":" << addr.port()); + + auto e = std::make_unique( + (const char *)data, size, addr); + + client_->send_event(std::move(e)); + } else { + LOG_WARNING("aoo_client: received unknown message " + << pattern << " from " << *this); + } + } catch (const osc::Exception& e){ + // peer exceptions are not fatal! + LOG_ERROR("aoo_client: " << *this << ": exception on handling " + << pattern << " message: " << e.what()); + } +} + +} // net +} // aoo diff --git a/deps/aoo/aoo/src/net/client.hpp b/deps/aoo/aoo/src/net/client.hpp new file mode 100644 index 00000000..e60c2065 --- /dev/null +++ b/deps/aoo/aoo/src/net/client.hpp @@ -0,0 +1,549 @@ +/* Copyright (c) 2010-Now Christof Ressi, Winfried Ritsch and others. + * For information on usage and redistribution, and for a DISCLAIMER OF ALL + * WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ + +#pragma once + +#include "aoo/aoo_net.hpp" +#include "aoo/aoo.hpp" + +#include "common/utils.hpp" +#include "common/sync.hpp" +#include "common/time.hpp" +#include "common/lockfree.hpp" +#include "common/net_utils.hpp" + +#include "../imp.hpp" +#include "commands.hpp" +#include "SLIP.hpp" + +#include "oscpack/osc/OscOutboundPacketStream.h" +#include "oscpack/osc/OscReceivedElements.h" + +#ifdef _WIN32 +#include +#endif + +#include +#include + +#ifndef AOO_NET_CLIENT_PING_INTERVAL + #define AOO_NET_CLIENT_PING_INTERVAL 5000 +#endif + +#ifndef AOO_NET_CLIENT_REQUEST_INTERVAL + #define AOO_NET_CLIENT_REQUEST_INTERVAL 100 +#endif + +#ifndef AOO_NET_CLIENT_REQUEST_TIMEOUT + #define AOO_NET_CLIENT_REQUEST_TIMEOUT 5000 +#endif + +namespace aoo { +namespace net { + +class client_imp; + +#if 0 +using ip_address_list = std::vector>; +#else +using ip_address_list = std::vector; +#endif + + +/*/////////////////////////// peer /////////////////////////*/ +class peer { +public: + peer(client_imp& client, int32_t id, const std::string& group, + const std::string& user, ip_address_list&& addrlist, int32_t flags=0); + + ~peer(); + + bool connected() const { + return connected_.load(std::memory_order_acquire); + } + + bool match(const ip_address& addr, bool unconnected = false) const; + + bool match(const std::string& group) const; + + bool match(const std::string& group, const std::string& user) const; + + bool match(const std::string& group, int32_t id); + + int32_t id() const { return id_; } + + uint32_t flags() const { return flags_; } + + bool relay() const { return flags_ & AOO_ENDPOINT_RELAY; } + + const std::string& group() const { return group_; } + + const std::string& user() const { return user_; } + + const ip_address& address() const { + return real_address_; + } + + void send(const sendfn& reply, time_tag now); + + void handle_message(const osc::ReceivedMessage& msg, int onset, + const ip_address& addr); + + friend std::ostream& operator << (std::ostream& os, const peer& p); +private: + client_imp *client_; + const int32_t id_; + uint32_t flags_ = 0; + const std::string group_; + const std::string user_; + ip_address_list addresses_; + ip_address real_address_; + time_tag start_time_; + double last_pingtime_ = 0; + std::atomic connected_{false}; + std::atomic got_ping_{false}; + bool timeout_ = false; + + bool handle_first_message(const osc::ReceivedMessage& msg, int onset, + const ip_address& addr); +}; + +/*///////////////////////// udp_client ///////////////////////////*/ + +class udp_client { +public: + udp_client(client_imp& c, int port, uint32_t flags) + : client_(&c), port_(port) {} + + int port() const { return port_; } + + aoo_error handle_message(const char *data, int32_t n, + const ip_address& addr, + int32_t type, aoo_type onset); + + void handle_relay_message(const osc::ReceivedMessage& msg); + + void update(const sendfn& reply, time_tag now); + + void send_server_message(const char *data, int32_t size, const sendfn& fn); + + void send_peer_message(const char *data, int32_t size, + const ip_address& addr, const sendfn& fn, bool relay); + + void start_handshake(const ip_address& local, ip_address_list&& remote); +private: + using scoped_lock = sync::scoped_lock; + using scoped_shared_lock = sync::scoped_shared_lock; + + client_imp *client_; + int port_; + ip_address local_address_; + ip_address_list server_addrlist_; + ip_address_list public_addrlist_; + sync::shared_mutex mutex_; + + double last_ping_time_ = 0; + std::atomic first_ping_time_{0}; + + void send_ping(); + + void handle_server_message(const osc::ReceivedMessage& msg, int onset); + + bool is_server_address(const ip_address& addr); +}; + +/*/////////////////////////////// client /////////////////////////////*/ + +enum class client_state { + disconnected, + connecting, + handshake, + login, + connected +}; + +class client_imp final : public client +{ +public: + struct icommand { + virtual ~icommand(){} + virtual void perform(client_imp&) = 0; + }; + + struct imessage { + virtual ~imessage(){} + virtual void perform(client_imp&, const sendfn& fn) = 0; + }; + + struct ievent { + virtual ~ievent(){} + + union { + aoo_event event_; + aoo_net_error_event error_event_; + aoo_net_ping_event ping_event_; + aoo_net_peer_event peer_event_; + aoo_net_message_event message_event_; + }; + }; + + client_imp(const void *address, int32_t addrlen, uint32_t flags); + + ~client_imp(); + + aoo_error run() override; + + aoo_error quit() override; + + aoo_error add_source(source *src, aoo_id id) override; + + aoo_error remove_source(source *src) override; + + aoo_error add_sink(sink *sink, aoo_id id) override; + + aoo_error remove_sink(sink *sink) override; + + aoo_error get_peer_address(const char *group, const char *user, + void *address, int32_t *addrlen, uint32_t *flags) override; + + aoo_error get_peer_info(const void *address, int32_t addrlen, + aoo_net_peer_info *info) override; + + aoo_error send_request(aoo_net_request_type request, void *data, + aoo_net_callback callback, void *user) override; + + aoo_error send_message(const char *data, int32_t size, + const void *addr, int32_t len, int32_t flags) override; + + template + void perform_send_message(const char *data, int32_t size, int32_t flags, + const sendfn& fn, T&& filter); + + aoo_error handle_message(const char *data, int32_t n, + const void *addr, int32_t len) override; + + bool handle_peer_message(const osc::ReceivedMessage& msg, int onset, + const ip_address& addr); + + aoo_error send(aoo_sendfn fn, void *user) override; + + aoo_error set_eventhandler(aoo_eventhandler fn, void *user, int32_t mode) override; + + aoo_bool events_available() override; + + aoo_error poll_events() override; + + aoo_error control(int32_t ctl, intptr_t index, void *ptr, size_t size) override; + + void do_connect(const char *host, int port, + const char *name, const char *pwd, + aoo_net_callback cb, void *user); + + void perform_connect(const std::string& host, int port, + const std::string& name, const std::string& pwd, + aoo_net_callback cb, void *user); + + int try_connect(const std::string& host, int port); + + void perform_login(const ip_address_list& addrlist); + + void perform_timeout(); + + void do_disconnect(aoo_net_callback cb, void *user); + + void perform_disconnect(aoo_net_callback cb, void *user); + + void do_join_group(const char *name, const char *pwd, + aoo_net_callback cb, void *user); + + void perform_join_group(const std::string& group, const std::string& pwd, + aoo_net_callback cb, void *user); + + void do_leave_group(const char *name, aoo_net_callback cb, void *user); + + void perform_leave_group(const std::string& group, + aoo_net_callback cb, void *user); + + double ping_interval() const { return ping_interval_.load(std::memory_order_relaxed); } + + double request_interval() const { return request_interval_.load(std::memory_order_relaxed); } + + double request_timeout() const { return request_timeout_.load(std::memory_order_relaxed); } + + void send_event(std::unique_ptr e); + + void push_command(std::unique_ptr&& cmd); + + udp_client& udp() { return *udp_client_; } + + ip_address::ip_type type() const { return type_; } + + double elapsed_time_since(time_tag now) const { + return time_tag::duration(start_time_, now); + } + + client_state current_state() const { return state_.load(); } + + bool have_server_flag(uint32_t flag) const { + return flag & server_flags_; + } +private: + std::unique_ptr udp_client_; + int socket_ = -1; + ip_address::ip_type type_ = ip_address::Unspec; + // dependants + struct source_desc { + aoo::source *source; + aoo_id id; + }; + std::vector> sources_; + struct sink_desc { + aoo::sink *sink; + aoo_id id; + }; + std::vector> sinks_; + // SLIP buffers + SLIP> sendbuffer_; + SLIP> recvbuffer_; + // event + std::atomic quit_{false}; + int eventsocket_ = -1; + // peers + using peer_list = lockfree::simple_list>; + using peer_lock = std::unique_lock; + peer_list peers_; + // time + time_tag start_time_; + double last_ping_time_ = 0; + // handshake + std::atomic state_{client_state::disconnected}; + std::string username_; + std::string password_; + uint32_t server_flags_ = 0; + aoo_net_callback callback_ = nullptr; + void *userdata_ = nullptr; + // commands + using icommand_ptr = std::unique_ptr; + using command_queue = lockfree::unbounded_mpsc_queue>; + command_queue commands_; + // peer/group messages + using imessage_ptr = std::unique_ptr; + using message_queue = lockfree::unbounded_mpsc_queue>; + message_queue udp_messages_; + message_queue tcp_messages_; + // pending request + using request = std::function; + std::vector> pending_requests_; + // events + using ievent_ptr = std::unique_ptr; + using event_queue = lockfree::unbounded_mpsc_queue>; + event_queue events_; + aoo_eventhandler eventhandler_ = nullptr; + void *eventcontext_ = nullptr; + aoo_event_mode eventmode_ = AOO_EVENT_NONE; + // options + std::atomic ping_interval_{AOO_NET_CLIENT_PING_INTERVAL * 0.001}; + std::atomic request_interval_{AOO_NET_CLIENT_REQUEST_INTERVAL * 0.001}; + std::atomic request_timeout_{AOO_NET_CLIENT_REQUEST_TIMEOUT * 0.001}; + + // methods + bool wait_for_event(float timeout); + + void receive_data(); + + bool signal(); + + void send_server_message(const char *data, int32_t size); + + void send_peer_message(const char *data, int32_t size, + const ip_address& addr); + + void handle_server_bundle(const osc::ReceivedBundle& bundle); + + void handle_server_message(const char *data, int32_t n); + + void handle_login(const osc::ReceivedMessage& msg); + + void handle_relay_message(const osc::ReceivedMessage& msg); + + void handle_peer_add(const osc::ReceivedMessage& msg); + + void handle_peer_remove(const osc::ReceivedMessage& msg); + + void on_socket_error(int err); + + void on_exception(const char *what, const osc::Exception& err, + const char *pattern = nullptr); + + void close(bool manual = false); + + /*////////////////////// events /////////////////////*/ +public: + struct event : ievent + { + event(int32_t type){ + event_.type = type; + } + }; + + struct error_event : ievent + { + error_event(int32_t code, const char *msg); + ~error_event(); + }; + + struct ping_event : ievent + { + ping_event(int32_t type, const ip_address& addr, + uint64_t tt1, uint64_t tt2, uint64_t tt3); + ~ping_event(); + }; + + struct peer_event : ievent + { + peer_event(int32_t type, const ip_address& addr, + const char *group, const char *user, + int32_t id, uint32_t flags); + peer_event(int32_t type, const char *group, + const char *user, int32_t id); + ~peer_event(); + }; + + struct message_event : ievent + { + message_event(const char *data, int32_t size, + const ip_address& addr); + ~message_event(); + }; + + /*////////////////////// commands ///////////////////*/ + struct request_cmd : icommand + { + request_cmd(aoo_net_callback cb, void *user) + : cb_(cb), user_(user){} + protected: + aoo_net_callback cb_; + void *user_; + }; + + struct connect_cmd : request_cmd + { + connect_cmd(aoo_net_callback cb, void *user, + const std::string &host, int port, + const std::string& name, const std::string& pwd) + : request_cmd(cb, user), host_(host), port_(port), + name_(name), pwd_(pwd) {} + + void perform(client_imp& obj) override { + obj.perform_connect(host_, port_, name_, pwd_, cb_, user_); + } + private: + std::string host_; + int port_; + std::string name_; + std::string pwd_; + }; + + struct disconnect_cmd : request_cmd + { + disconnect_cmd(aoo_net_callback cb, void *user) + : request_cmd(cb, user) {} + + void perform(client_imp& obj) override { + obj.perform_disconnect(cb_, user_); + } + }; + + struct login_cmd : icommand + { + login_cmd(ip_address_list&& addrlist) + : addrlist_(std::move(addrlist)) {} + + void perform(client_imp& obj) override { + obj.perform_login(addrlist_); + } + private: + ip_address_list addrlist_; + }; + + struct timeout_cmd : icommand + { + void perform(client_imp& obj) override { + obj.perform_timeout(); + } + }; + + struct group_join_cmd : request_cmd + { + group_join_cmd(aoo_net_callback cb, void *user, + const std::string& group, const std::string& pwd) + : request_cmd(cb, user), group_(group), password_(pwd){} + + void perform(client_imp& obj) override { + obj.perform_join_group(group_, password_, cb_, user_); + } + private: + std::string group_; + std::string password_; + }; + + struct group_leave_cmd : request_cmd + { + group_leave_cmd(aoo_net_callback cb, void *user, + const std::string& group) + : request_cmd(cb, user), group_(group){} + + void perform(client_imp& obj) override { + obj.perform_leave_group(group_, cb_, user_); + } + private: + std::string group_; + }; + + /*////////////////// messages ////////////////////*/ + + struct message : imessage { + message(const char *data, int32_t size, int32_t flags) + : flags_(flags) { + data_.assign(data, data + size); + } + + void perform(client_imp& obj, const sendfn& fn) override { + obj.perform_send_message(data_.data(), data_.size(), + flags_, fn, [](auto&){ return true; }); + } + protected: + std::vector> data_; + int32_t flags_; + }; + + struct peer_message : message { + peer_message(const char *data, int32_t size, + const sockaddr *addr, int32_t len, int32_t flags) + : message(data, size, flags), address_(addr, len) {} + + void perform(client_imp& obj, const sendfn& fn) override { + obj.perform_send_message(data_.data(), data_.size(), + flags_, fn, [&](auto& peer){ return peer.match(address_); }); + } + protected: + ip_address address_; + }; + + struct group_message : message { + group_message(const char *data, int32_t size, + const char *group, int32_t flags) + : message(data, size, flags), group_(group) {} + + void perform(client_imp& obj, const sendfn& fn) override { + obj.perform_send_message(data_.data(), data_.size(), + flags_, fn, [&](auto& peer){ return peer.match(group_); }); + } + protected: + std::string group_; + }; +}; + +} // net +} // aoo diff --git a/deps/aoo/aoo/src/net/commands.hpp b/deps/aoo/aoo/src/net/commands.hpp new file mode 100644 index 00000000..26599e57 --- /dev/null +++ b/deps/aoo/aoo/src/net/commands.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "aoo/aoo_net.h" + +#define AOO_NET_MSG_PING "/ping" +#define AOO_NET_MSG_PING_LEN 5 + +#define AOO_NET_MSG_MESSAGE "/msg" +#define AOO_NET_MSG_MESSAGE_LEN 4 + +#define AOO_NET_MSG_LOGIN "/login" +#define AOO_NET_MSG_LOGIN_LEN 6 + +#define AOO_NET_MSG_REQUEST "/request" +#define AOO_NET_MSG_REQUEST_LEN 8 + +#define AOO_NET_MSG_REPLY "/reply" +#define AOO_NET_MSG_REPLY_LEN 6 + +#define AOO_NET_MSG_GROUP "/group" +#define AOO_NET_MSG_GROUP_LEN 6 + +#define AOO_NET_MSG_JOIN "/join" +#define AOO_NET_MSG_JOIN_LEN 5 + +#define AOO_NET_MSG_LEAVE "/leave" +#define AOO_NET_MSG_LEAVE_LEN 6 diff --git a/deps/aoo/aoo/src/net/server.cpp b/deps/aoo/aoo/src/net/server.cpp new file mode 100644 index 00000000..8b12e776 --- /dev/null +++ b/deps/aoo/aoo/src/net/server.cpp @@ -0,0 +1,1138 @@ +/* Copyright (c) 2010-Now Christof Ressi, Winfried Ritsch and others. + * For information on usage and redistribution, and for a DISCLAIMER OF ALL + * WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ + +#include "server.hpp" + +#include "aoo/aoo.h" + +#include +#include +#include + +#define AOO_NET_MSG_CLIENT_PING \ + AOO_MSG_DOMAIN AOO_NET_MSG_CLIENT AOO_NET_MSG_PING + +#define AOO_NET_MSG_CLIENT_LOGIN \ + AOO_MSG_DOMAIN AOO_NET_MSG_CLIENT AOO_NET_MSG_LOGIN + +#define AOO_NET_MSG_CLIENT_REPLY \ + AOO_MSG_DOMAIN AOO_NET_MSG_CLIENT AOO_NET_MSG_REPLY + +#define AOO_NET_MSG_CLIENT_GROUP_JOIN \ + AOO_MSG_DOMAIN AOO_NET_MSG_CLIENT AOO_NET_MSG_GROUP AOO_NET_MSG_JOIN + +#define AOO_NET_MSG_CLIENT_GROUP_LEAVE \ + AOO_MSG_DOMAIN AOO_NET_MSG_CLIENT AOO_NET_MSG_GROUP AOO_NET_MSG_LEAVE + +#define AOO_NET_MSG_CLIENT_PEER_JOIN \ + AOO_MSG_DOMAIN AOO_NET_MSG_CLIENT AOO_NET_MSG_PEER AOO_NET_MSG_JOIN + +#define AOO_NET_MSG_CLIENT_PEER_LEAVE \ + AOO_MSG_DOMAIN AOO_NET_MSG_CLIENT AOO_NET_MSG_PEER AOO_NET_MSG_LEAVE + +#define AOO_NET_MSG_GROUP_JOIN \ + AOO_NET_MSG_GROUP AOO_NET_MSG_JOIN + +#define AOO_NET_MSG_GROUP_LEAVE \ + AOO_NET_MSG_GROUP AOO_NET_MSG_LEAVE + +/*//////////////////// AoO server /////////////////////*/ + +aoo_net_server * aoo_net_server_new(int port, uint32_t flags, aoo_error *err) { + // create UDP socket + int udpsocket = aoo::socket_udp(port); + if (udpsocket < 0){ + auto e = aoo::socket_errno(); + *err = AOO_ERROR_UNSPECIFIED; + LOG_ERROR("aoo_server: couldn't create UDP socket (" << e << ")"); + return nullptr; + } + + // increase UDP receive buffer size to 16 MB + if (aoo::socket_setrecvbufsize(udpsocket, 1<<24) < 0){ + aoo::socket_error_print("setrecvbufsize"); + } + + // create TCP socket + int tcpsocket = aoo::socket_tcp(port); + if (tcpsocket < 0){ + auto e = aoo::socket_errno(); + *err = AOO_ERROR_UNSPECIFIED; + LOG_ERROR("aoo_server: couldn't create TCP socket (" << e << ")"); + aoo::socket_close(udpsocket); + return nullptr; + } + + // listen + if (listen(tcpsocket, 32) < 0){ + auto e = aoo::socket_errno(); + *err = AOO_ERROR_UNSPECIFIED; + LOG_ERROR("aoo_server: listen() failed (" << e << ")"); + aoo::socket_close(tcpsocket); + aoo::socket_close(udpsocket); + return nullptr; + } + + return aoo::construct(tcpsocket, udpsocket); +} + +aoo::net::server_imp::server_imp(int tcpsocket, int udpsocket) + : tcpsocket_(tcpsocket), udpserver_(udpsocket) +{ + eventsocket_ = socket_udp(0); + type_ = socket_family(udpsocket); + // commands_.reserve(256); + // events_.reserve(256); +} + +void aoo_net_server_free(aoo_net_server *server){ + // cast to correct type because base class + // has no virtual destructor! + aoo::destroy(static_cast(server)); +} + +aoo::net::server_imp::~server_imp() { + socket_close(tcpsocket_); + tcpsocket_ = -1; + + // clear explicitly to avoid crash! + clients_.clear(); +} + +aoo_error aoo_net_server_run(aoo_net_server *server){ + return server->run(); +} + +aoo_error aoo::net::server_imp::run(){ + // wait for networking or other events + while (!quit_.load()){ + if (!receive()){ + break; + } + } + + if (!notify_on_shutdown_.load()){ + // JC: need to close all the clients sockets without + // having them send anything out, so that active communication + // between connected peers can continue if the server goes down for maintainence + for (auto& client : clients_){ + client.close(false); + } + } + + return AOO_OK; +} + +aoo_error aoo_net_server_quit(aoo_net_server *server){ + return server->quit(); +} + +aoo_error aoo::net::server_imp::quit(){ + // set quit and wake up receive thread + quit_.store(true); + if (!socket_signal(eventsocket_)){ + // force wakeup by closing the socket. + // this is not nice and probably undefined behavior, + // the MSDN docs explicitly forbid it! + socket_close(eventsocket_); + } + return AOO_OK; +} + +aoo_error aoo_net_server_set_eventhandler(aoo_net_client *sink, aoo_eventhandler fn, + void *user, int32_t mode) +{ + return sink->set_eventhandler(fn, user, mode); +} + +aoo_error aoo::net::server_imp::set_eventhandler(aoo_eventhandler fn, void *user, + int32_t mode) +{ + eventhandler_ = fn; + eventcontext_ = user; + eventmode_ = (aoo_event_mode)mode; + return AOO_OK; +} + +aoo_bool aoo_net_server_events_available(aoo_net_server *server){ + return server->events_available(); +} + +aoo_bool aoo::net::server_imp::events_available(){ + return !events_.empty(); +} + +aoo_error aoo_net_server_poll_events(aoo_net_server *server){ + return server->poll_events(); +} + +aoo_error aoo::net::server_imp::poll_events(){ + // always thread-safe + std::unique_ptr e; + while (events_.try_pop(e)){ + eventhandler_(eventcontext_, &e->event_, AOO_THREAD_UNKNOWN); + } + return AOO_OK; +} + +aoo_error aoo_net_server_ctl(aoo_net_server *server, int32_t ctl, + intptr_t index, void *p, size_t size) +{ + return server->control(ctl, index, p, size); +} + +aoo_error aoo::net::server_imp::control(int32_t ctl, intptr_t index, + void *ptr, size_t size) +{ + LOG_WARNING("aoo_server: unsupported control " << ctl); + return AOO_ERROR_UNSPECIFIED; +} + +namespace aoo { +namespace net { + +std::string server_imp::error_to_string(error e){ + switch (e){ + case server_imp::error::access_denied: + return "access denied"; + case server_imp::error::permission_denied: + return "permission denied"; + case server_imp::error::wrong_password: + return "wrong password"; + default: + return "unknown error"; + } +} + +std::shared_ptr server_imp::get_user(const std::string& name, + const std::string& pwd, + uint32_t version, error& e) +{ + auto usr = find_user(name); + if (usr){ + // check if someone is already logged in + if (usr->active()){ + e = error::access_denied; + return nullptr; + } + // check password for existing user + if (usr->password == pwd){ + e = error::none; + return usr; + } else { + e = error::wrong_password; + return nullptr; + } + } else { + // create new user (LATER add option to disallow this) + if (true){ + auto id = get_next_user_id(); + usr = std::make_shared(name, pwd, id, version); + users_.push_back(usr); + e = error::none; + return usr; + } else { + e = error::permission_denied; + return nullptr; + } + } +} + +std::shared_ptr server_imp::find_user(const std::string& name) +{ + for (auto& usr : users_){ + if (usr->name == name){ + return usr; + } + } + return nullptr; +} + +std::shared_ptr server_imp::get_group(const std::string& name, + const std::string& pwd, error& e) +{ + auto grp = find_group(name); + if (grp){ + // check password for existing group + if (grp->password == pwd){ + e = error::none; + return grp; + } else { + e = error::wrong_password; + return nullptr; + } + } else { + // create new group (LATER add option to disallow this) + if (true){ + grp = std::make_shared(name, pwd); + groups_.push_back(grp); + e = error::none; + return grp; + } else { + e = error::permission_denied; + return nullptr; + } + } +} + +std::shared_ptr server_imp::find_group(const std::string& name) +{ + for (auto& grp : groups_){ + if (grp->name == name){ + return grp; + } + } + return nullptr; +} + + +void server_imp::on_user_joined(user &usr){ + auto e = std::make_unique(AOO_NET_USER_JOIN_EVENT, + usr.name.c_str(), usr.id, + usr.endpoint()->local_address()); // do we need this? + send_event(std::move(e)); +} + +void server_imp::on_user_left(user &usr){ + auto e = std::make_unique(AOO_NET_USER_LEAVE_EVENT, + usr.name.c_str(), usr.id, + usr.endpoint()->local_address()); // do we need this? + send_event(std::move(e)); +} + +void server_imp::on_user_joined_group(user& usr, group& grp){ + // 1) send the new member to existing group members + // 2) send existing group members to the new member + for (auto& peer : grp.users()){ + if (peer->id != usr.id){ + char buf[AOO_MAXPACKETSIZE]; + + auto notify = [&](client_endpoint* dest, user& u){ + osc::OutboundPacketStream msg(buf, sizeof(buf)); + msg << osc::BeginMessage(AOO_NET_MSG_CLIENT_PEER_JOIN) + << grp.name.c_str() << u.name.c_str(); + + user & destusr = (&u == peer.get()) ? usr : *peer; + // only v0.2-pre3 and abvoe + if (destusr.version > 0){ + msg << u.id; + } + // send *unmapped* addresses in case the client is IPv4 only + int cnt = 0; + auto addrcnt = u.endpoint()->public_addresses().size(); + for (auto& addr : u.endpoint()->public_addresses()){ + if (destusr.legacy && addrcnt > 2) { + --addrcnt; + continue; + } + msg << addr.name_unmapped() << addr.port(); + LOG_VERBOSE("aoo_server: dest: " << destusr.name << "peeraddr: " << addr.name_unmapped() << ":" << addr.port()); + } + msg << osc::EndMessage; + + dest->send_message(msg.Data(), msg.Size()); + }; + + // notify new member + notify(usr.endpoint(), *peer); + + // notify existing member + notify(peer->endpoint(), usr); + } + } + + auto e = std::make_unique(AOO_NET_GROUP_JOIN_EVENT, + grp.name.c_str(), + usr.name.c_str(), usr.id); + send_event(std::move(e)); +} + +void server_imp::on_user_left_group(user& usr, group& grp){ + if (tcpsocket_ < 0){ + return; // prevent sending messages during shutdown + } + // notify group members + for (auto& peer : grp.users()){ + if (peer->id != usr.id){ + char buf[AOO_MAXPACKETSIZE]; + osc::OutboundPacketStream msg(buf, sizeof(buf)); + msg << osc::BeginMessage(AOO_NET_MSG_CLIENT_PEER_LEAVE) + << grp.name.c_str() << usr.name.c_str() << usr.id + << osc::EndMessage; + + peer->endpoint()->send_message(msg.Data(), msg.Size()); + } + } + + auto e = std::make_unique(AOO_NET_GROUP_LEAVE_EVENT, + grp.name.c_str(), + usr.name.c_str(), usr.id); + send_event(std::move(e)); +} + +void server_imp::handle_relay_message(const osc::ReceivedMessage& msg, + const ip_address& src){ + auto it = msg.ArgumentsBegin(); + + auto ip = (it++)->AsString(); + auto port = (it++)->AsInt32(); + ip_address dst(ip, port, type()); + + const void *msgData; + osc::osc_bundle_element_size_t msgSize; + (it++)->AsBlob(msgData, msgSize); + + // forward message to matching client + // send unmapped address in case the client is IPv4 only! + char buf[AOO_MAXPACKETSIZE]; + osc::OutboundPacketStream out(buf, sizeof(buf)); + out << osc::BeginMessage(AOO_MSG_DOMAIN AOO_NET_MSG_RELAY) + << src.name_unmapped() << src.port() << osc::Blob(msgData, msgSize) + << osc::EndMessage; + + for (auto& client : clients_){ + if (client.match(dst)) { + client.send_message(out.Data(), out.Size()); + return; + } + } + LOG_WARNING("aoo_server: couldn't find matching client for relay message"); +} + +void server_imp::send_event(std::unique_ptr e){ + switch (eventmode_){ + case AOO_EVENT_POLL: + events_.push(std::move(e)); + break; + case AOO_EVENT_CALLBACK: + // server only has network threads + eventhandler_(eventcontext_, &e->event_, AOO_THREAD_NETWORK); + break; + default: + break; + } +} + +int32_t server_imp::get_next_user_id(){ + // LATER make random user ID + return next_user_id_++; +} + +bool server_imp::receive(){ + bool didclose = false; + int numclients = clients_.size(); + + // initialize pollfd array + // allocate extra slots for main TCP socket and event socket + pollarray_.resize(numclients + 2); + + for (int i = 0; i < (int)pollarray_.size(); ++i){ + pollarray_[i].events = POLLIN; + pollarray_[i].revents = 0; + } + + int index = 0; + for (auto& c : clients_){ + pollarray_[index++].fd = c.socket(); + } + + auto& tcp_fd = pollarray_[numclients]; + tcp_fd.fd = tcpsocket_; + + auto& event_fd = pollarray_[numclients+1]; + event_fd.fd = eventsocket_; + + // NOTE: macOS/BSD requires the negative timeout to be exactly -1! +#ifdef _WIN32 + int result = WSAPoll(pollarray_.data(), pollarray_.size(), -1); +#else + int result = poll(pollarray_.data(), pollarray_.size(), -1); +#endif + if (result < 0){ + int err = errno; + if (err == EINTR){ + return true; // ? + } else { + LOG_ERROR("aoo_server: poll failed (" << err << ")"); + return false; + } + } + + if (event_fd.revents){ + return false; // quit + } + + if (tcp_fd.revents){ + // accept new client + ip_address addr; + auto sock = accept(tcpsocket_, addr.address_ptr(), addr.length_ptr()); + if (sock >= 0){ + clients_.emplace_back(*this, sock, addr); + LOG_VERBOSE("aoo_server: accepted client (IP: " + << addr.name() << ", port: " << addr.port() << ")"); + } else { + int err = socket_errno(); + LOG_ERROR("aoo_server: couldn't accept client (" << err << ")"); + } + } + + // use original number of clients! + index = 0; + for (auto it = clients_.begin(); index < numclients; ++index, ++it){ + if (pollarray_[index].revents){ + // receive data from client + if (!it->receive_data()){ + LOG_VERBOSE("aoo_server: close client"); + it->close(); + didclose = true; + } + } + } + + if (didclose){ + update(); + } + + return true; +} + +void server_imp::update(){ + clients_.remove_if([](auto& c){ return !c.active(); }); + // automatically purge stale users + // LATER add an option so that users will persist + for (auto it = users_.begin(); it != users_.end(); ){ + if (!(*it)->active()){ + it = users_.erase(it); + } else { + ++it; + } + } + // automatically purge empty groups + // LATER add an option so that groups will persist + for (auto it = groups_.begin(); it != groups_.end(); ){ + if ((*it)->num_users() == 0){ + it = groups_.erase(it); + } else { + ++it; + } + } +} + +uint32_t server_imp::flags() const { + uint32_t flags = 0; + if (allow_relay_.load(std::memory_order_relaxed)){ + flags |= AOO_NET_SERVER_RELAY; + } + return flags; +} + +/*////////////////////////// udp_server /////////////////////*/ + +udp_server::udp_server(int socket) { + socket_ = socket; + type_ = socket_family(socket); + receivethread_ = std::thread(&udp_server::receive_packets, this); + workerthread_ = std::thread(&udp_server::handle_packets, this); +} + +udp_server::~udp_server(){ + quit_ = true; + event_.set(); + socket_signal(socket_); + + if (receivethread_.joinable()){ + receivethread_.join(); + } + + if (workerthread_.joinable()){ + workerthread_.join(); + } + + socket_close(socket_); +} + +void udp_server::receive_packets(){ + while (!quit_.load(std::memory_order_relaxed)){ + ip_address addr; + char buf[AOO_MAXPACKETSIZE]; + int nbytes = socket_receive(socket_, buf, AOO_MAXPACKETSIZE, &addr, -1); + if (nbytes > 0){ + // add packet to queue + recvbuffer_.produce([&](udp_packet& p){ + p.address = addr; + p.data.assign(buf, buf + nbytes); + }); + #if DEBUG_THREADS + recvbufferfill_.fetch_add(1, std::memory_order_relaxed); + #endif + event_.set(); + } else if (nbytes < 0) { + // ignore errors when quitting + if (!quit_){ + socket_error_print("recv"); + } + break; + } + // ignore empty packets (used for quit signalling) + #if DEBUG_THREADS + std::cout << "receive_packets: waiting" << std::endl; + #endif + } +} + +void udp_server::handle_packets(){ + while (!quit_.load(std::memory_order_relaxed)){ + event_.wait(); + + while (!recvbuffer_.empty()){ + #if DEBUG_THREADS + std::cout << "perform_io: handle_message" << std::endl; + #endif + recvbuffer_.consume([&](const udp_packet& p){ + handle_packet(p.data.data(), p.data.size(), p.address); + }); + #if DEBUG_THREADS + auto fill = recvbufferfill_.fetch_sub(1, std::memory_order_relaxed) - 1; + std::cerr << "receive buffer fill: " << fill << std::endl; + #endif + } + } +} + +void udp_server::handle_packet(const char *data, int32_t size, const ip_address& addr){ + try { + osc::ReceivedPacket packet(data, size); + osc::ReceivedMessage msg(packet); + + aoo_type type; + int32_t onset; + auto err = parse_pattern(data, size, type, onset); + if (err != AOO_OK){ + LOG_WARNING("aoo_server: not an AOO NET message!"); + return; + } + + if (type == AOO_TYPE_SERVER){ + handle_message(msg, onset, addr); + } else if (type == AOO_TYPE_RELAY){ + handle_relay_message(msg, addr); + } else { + LOG_WARNING("aoo_server: not a client message!"); + return; + } + } catch (const osc::Exception& e){ + LOG_ERROR("aoo_server: exception in receive_udp: " << e.what()); + // ignore for now + } +} + +void udp_server::handle_message(const osc::ReceivedMessage &msg, int onset, + const ip_address& addr) +{ + auto pattern = msg.AddressPattern() + onset; + LOG_DEBUG("aoo_server: handle client UDP message " << pattern); + + try { + if (!strcmp(pattern, AOO_NET_MSG_PING)){ + // reply with /ping message + char buf[512]; + osc::OutboundPacketStream reply(buf, sizeof(buf)); + reply << osc::BeginMessage(AOO_NET_MSG_CLIENT_PING) + << osc::EndMessage; + + send_message(reply.Data(), reply.Size(), addr); + } else if (!strcmp(pattern, AOO_NET_MSG_REQUEST)){ + // reply with /reply message + // send *unmapped* address in case the client is IPv4 only + char buf[512]; + osc::OutboundPacketStream reply(buf, sizeof(buf)); + reply << osc::BeginMessage(AOO_NET_MSG_CLIENT_REPLY) + << addr.name_unmapped() << addr.port() + << osc::EndMessage; + + send_message(reply.Data(), reply.Size(), addr); + } else { + LOG_ERROR("aoo_server: unknown message " << pattern); + } + } catch (const osc::Exception& e){ + LOG_ERROR("aoo_server: exception on handling " << pattern + << " message: " << e.what()); + // ignore for now + } +} + +void udp_server::handle_relay_message(const osc::ReceivedMessage& msg, + const ip_address& src){ + auto it = msg.ArgumentsBegin(); + + auto ip = (it++)->AsString(); + auto port = (it++)->AsInt32(); + ip_address dst(ip, port, type_); + + const void *msgData; + osc::osc_bundle_element_size_t msgSize; + (it++)->AsBlob(msgData, msgSize); + + // forward message to matching client + // send unmapped address in case the client is IPv4 only! + char buf[AOO_MAXPACKETSIZE]; + osc::OutboundPacketStream out(buf, sizeof(buf)); + out << osc::BeginMessage(AOO_MSG_DOMAIN AOO_NET_MSG_RELAY) + << src.name_unmapped() << src.port() << osc::Blob(msgData, msgSize) + << osc::EndMessage; + + send_message(out.Data(), out.Size(), dst); +} + +void udp_server::send_message(const char *msg, int32_t size, + const ip_address &addr) +{ + int result = ::sendto(socket_, msg, size, 0, + addr.address(), addr.length()); + if (result < 0){ + int err = socket_errno(); + // TODO handle error + LOG_ERROR("aoo_server: send() failed (" << err << ")"); + } +} + +/*////////////////////////// user ///////////////////////////*/ + +void user::on_close(server_imp& s){ + // disconnect user from groups + for (auto& grp : groups_){ + grp->remove_user(*this); + s.on_user_left_group(*this, *grp); + } + + s.on_user_left(*this); + + groups_.clear(); + // clear endpoint so the server knows it can remove the user + endpoint_ = nullptr; +} + +bool user::add_group(std::shared_ptr grp){ + auto it = std::find(groups_.begin(), groups_.end(), grp); + if (it == groups_.end()){ + groups_.push_back(grp); + return true; + } else { + return false; + } +} + +bool user::remove_group(const group& grp){ + // find by address + auto it = std::find_if(groups_.begin(), groups_.end(), + [&](auto& g){ return g.get() == &grp; }); + if (it != groups_.end()){ + groups_.erase(it); + return true; + } else { + return false; + } +} + +/*////////////////////////// group /////////////////////////*/ + +bool group::add_user(std::shared_ptr grp){ + auto it = std::find(users_.begin(), users_.end(), grp); + if (it == users_.end()){ + users_.push_back(grp); + return true; + } else { + LOG_ERROR("group::add_user: bug"); + return false; + } +} + +bool group::remove_user(const user& usr){ + // find by address + auto it = std::find_if(users_.begin(), users_.end(), + [&](auto& u){ return u.get() == &usr; }); + if (it != users_.end()){ + users_.erase(it); + return true; + } else { + LOG_ERROR("group::remove_user: bug"); + return false; + } +} + +/*///////////////////////// client_endpoint /////////////////////////////*/ + +client_endpoint::client_endpoint(server_imp &s, int socket, const ip_address &addr) + : server_(&s), socket_(socket), addr_(addr) +{ + // set TCP_NODELAY - do we need to do this? + int val = 1; + if (setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(val)) < 0){ + LOG_WARNING("client_endpoint: couldn't set TCP_NODELAY"); + // ignore + } + + sendbuffer_.setup(65536); + recvbuffer_.setup(65536); +} + +client_endpoint::~client_endpoint(){ + close(); +} + +bool client_endpoint::match(const ip_address& addr) const { + // match public UDP addresses! + for (auto& a : public_addresses_){ + if (a == addr){ + return true; + } + } + return false; +} + +void client_endpoint::close(bool notify){ + if (socket_ >= 0){ + LOG_VERBOSE("aoo_server: close client endpoint " + << addr_.name() << " " << addr_.port()); + socket_close(socket_); + socket_ = -1; + + if (user_ && notify){ + user_->on_close(*server_); + } + } +} + +void client_endpoint::send_message(const char *msg, int32_t size){ + if (sendbuffer_.write_packet((const uint8_t *)msg, size)){ + while (sendbuffer_.read_available()){ + uint8_t buf[1024]; + int32_t total = sendbuffer_.read_bytes(buf, sizeof(buf)); + + int32_t nbytes = 0; + while (nbytes < total){ + auto res = ::send(socket_, (char *)buf + nbytes, total - nbytes, 0); + if (res >= 0){ + nbytes += res; + #if 0 + LOG_VERBOSE("aoo_server: sent " << res << " bytes"); + #endif + } else { + auto err = socket_errno(); + // TODO handle error + LOG_ERROR("aoo_server: send() failed (" << err << ")"); + return; + } + } + } + LOG_DEBUG("aoo_server: sent " << msg << " to client"); + } else { + LOG_ERROR("aoo_server: couldn't send " << msg << " to client"); + } +} + +bool client_endpoint::receive_data(){ + char buffer[AOO_MAXPACKETSIZE]; + auto result = recv(socket_, buffer, sizeof(buffer), 0); + if (result == 0){ + LOG_WARNING("client_endpoint: connection was closed"); + return false; + } + if (result < 0){ + int err = socket_errno(); + // TODO handle error + LOG_ERROR("client_endpoint: recv() failed (" << err << ")"); + return false; + } + + recvbuffer_.write_bytes((uint8_t *)buffer, result); + + // handle packets + uint8_t buf[AOO_MAXPACKETSIZE]; + int32_t size; + while ((size = recvbuffer_.read_packet(buf, sizeof(buf))) > 0){ + try { + osc::ReceivedPacket packet((char *)buf, size); + if (packet.IsBundle()){ + osc::ReceivedBundle bundle(packet); + if (!handle_bundle(bundle)){ + return false; + } + } else { + if (!handle_message(packet.Contents(), packet.Size())){ + return false; + } + } + } catch (const osc::Exception& e){ + LOG_ERROR("aoo_server: exception in client_endpoint::receive_data: " << e.what()); + return false; // close + } + } + return true; +} + +bool client_endpoint::handle_message(const char *data, int32_t n){ + osc::ReceivedPacket packet(data, n); + osc::ReceivedMessage msg(packet); + + aoo_type type; + int32_t onset; + auto err = parse_pattern(data, n, type, onset); + if (err != AOO_OK){ + LOG_WARNING("aoo_server: not an AOO NET message!"); + return false; + } + + try { + if (type == AOO_TYPE_SERVER){ + auto pattern = msg.AddressPattern() + onset; + LOG_DEBUG("aoo_server: got server message " << pattern); + if (!strcmp(pattern, AOO_NET_MSG_PING)){ + handle_ping(msg); + } else if (!strcmp(pattern, AOO_NET_MSG_LOGIN)){ + handle_login(msg); + } else if (!strcmp(pattern, AOO_NET_MSG_GROUP_JOIN)){ + handle_group_join(msg); + } else if (!strcmp(pattern, AOO_NET_MSG_GROUP_LEAVE)){ + handle_group_leave(msg); + } else { + LOG_ERROR("aoo_server: unknown server message " << pattern); + // LEGACY: ignore unknown server messages for now + //return false; + } + } else if (type == AOO_TYPE_RELAY){ + // use public address! + server_->handle_relay_message(msg, public_addresses().front()); + } else { + LOG_WARNING("aoo_client: got unexpected message " << msg.AddressPattern()); + return false; + } + + return true; + } catch (const osc::Exception& e){ + LOG_ERROR("aoo_server: exception on handling " << msg.AddressPattern() + << " message: " << e.what()); + return false; + } +} + +bool client_endpoint::handle_bundle(const osc::ReceivedBundle &bundle){ + auto it = bundle.ElementsBegin(); + while (it != bundle.ElementsEnd()){ + if (it->IsBundle()){ + osc::ReceivedBundle b(*it); + if (!handle_bundle(b)){ + return false; + } + } else { + if (!handle_message(it->Contents(), it->Size())){ + return false; + } + } + ++it; + } + return true; +} + +void client_endpoint::handle_ping(const osc::ReceivedMessage& msg){ + // send reply + char buf[AOO_MAXPACKETSIZE]; + osc::OutboundPacketStream reply(buf, sizeof(buf)); + reply << osc::BeginMessage(AOO_NET_MSG_CLIENT_PING) << osc::EndMessage; + + send_message(reply.Data(), reply.Size()); +} + +void client_endpoint::handle_login(const osc::ReceivedMessage& msg) +{ + int32_t result = 0; + uint32_t version = 0; + std::string errmsg; + bool legacy = false; + + auto it = msg.ArgumentsBegin(); + auto count = msg.ArgumentCount(); + if (count > 6 && msg.TypeTags()[0] == 'i'){ + // this is the latest format (version as integer 1st parameter) + version = (uint32_t)(it++)->AsInt32(); + count--; + } + else if (count > 6 && msg.TypeTags()[6] == 'h'){ + // LEGACY: this is to handle older sonobus clients that have a token as the 7th + legacy = true; + LOG_VERBOSE("aoo_server: client login is legacy"); + count--; + } + + // for now accept login messages without version. + // LATER they should fail, so clients have to upgrade. + if (version == 0 || check_version(version)){ + std::string username = (it++)->AsString(); + std::string password = (it++)->AsString(); + count -= 2; + + server_imp::error err; + if (!user_){ + user_ = server_->get_user(username, password, version, err); + if (user_){ + // success - collect addresses + while (count >= 2){ + std::string ip = (it++)->AsString(); + int32_t port = (it++)->AsInt32(); + ip_address addr(ip, port, server_->type()); + if (addr.valid()){ + public_addresses_.push_back(addr); + } + count -= 2; + } + user_->set_endpoint(this); + user_->legacy = legacy; + + LOG_VERBOSE("aoo_server: login: id: " << user_->id + << ", username: " << username << ", password: " << password); + + result = 1; + + server_->on_user_joined(*user_); + } else { + errmsg = server_imp::error_to_string(err); + } + } else { + errmsg = "already logged in"; // shouldn't happen + } + } else { + errmsg = "version not supported"; + } + // send reply + char buf[AOO_MAXPACKETSIZE]; + osc::OutboundPacketStream reply(buf, sizeof(buf)); + reply << osc::BeginMessage(AOO_NET_MSG_CLIENT_LOGIN) << result; + if (result && !legacy){ + reply << user_->id; + reply << (int32_t)server_->flags(); + } else { + reply << errmsg.c_str(); + } + reply << osc::EndMessage; + + send_message(reply.Data(), reply.Size()); +} + +void client_endpoint::handle_group_join(const osc::ReceivedMessage& msg) +{ + int result = 0; + std::string errmsg; + + auto it = msg.ArgumentsBegin(); + std::string name = (it++)->AsString(); + std::string password = (it++)->AsString(); + + server_imp::error err; + if (user_){ + auto grp = server_->get_group(name, password, err); + if (grp){ + if (user_->add_group(grp)){ + grp->add_user(user_); + server_->on_user_joined_group(*user_, *grp); + result = 1; + } else { + errmsg = "already a group member"; + } + } else { + errmsg = server_imp::error_to_string(err); + } + } else { + errmsg = "not logged in"; + } + + // send reply + char buf[AOO_MAXPACKETSIZE]; + osc::OutboundPacketStream reply(buf, sizeof(buf)); + reply << osc::BeginMessage(AOO_NET_MSG_CLIENT_GROUP_JOIN) + << name.c_str() << result << errmsg.c_str() << osc::EndMessage; + + send_message(reply.Data(), reply.Size()); +} + +void client_endpoint::handle_group_leave(const osc::ReceivedMessage& msg){ + int result = 0; + std::string errmsg; + + auto it = msg.ArgumentsBegin(); + std::string name = (it++)->AsString(); + + if (user_){ + auto grp = server_->find_group(name); + if (grp){ + if (user_->remove_group(*grp)){ + grp->remove_user(*user_); + server_->on_user_left_group(*user_, *grp); + result = 1; + } else { + errmsg = "not a group member"; + } + } else { + errmsg = "couldn't find group"; + } + } else { + errmsg = "not logged in"; + } + + // send reply + char buf[AOO_MAXPACKETSIZE]; + osc::OutboundPacketStream reply(buf, sizeof(buf)); + reply << osc::BeginMessage(AOO_NET_MSG_CLIENT_GROUP_LEAVE) + << name.c_str() << result << errmsg.c_str() << osc::EndMessage; + + send_message(reply.Data(), reply.Size()); +} + +/*///////////////////// events ////////////////////////*/ + +server_imp::error_event::error_event(int32_t type, int32_t code, + const char * msg) +{ + error_event_.type = type; + error_event_.error_code = code; + error_event_.error_message = copy_string(msg); +} + +server_imp::error_event::~error_event() +{ + free_string((char *)error_event_.error_message); +} + +server_imp::user_event::user_event(int32_t type, + const char *name, int32_t id, + const ip_address& address){ + user_event_.type = type; + user_event_.user_name = copy_string(name); + user_event_.user_id = id; + user_event_.address = copy_sockaddr(address.address(), address.length()); + user_event_.addrlen = address.length(); +} + +server_imp::user_event::~user_event() +{ + free_string((char *)user_event_.user_name); + free_sockaddr((void *)user_event_.address, user_event_.addrlen); +} + +server_imp::group_event::group_event(int32_t type, const char *group, + const char *user, int32_t id) +{ + group_event_.type = type; + group_event_.group_name = copy_string(group); + group_event_.user_name = copy_string(user); + group_event_.user_id = id; +} + +server_imp::group_event::~group_event() +{ + free_string((char *)group_event_.group_name); + free_string((char *)group_event_.user_name); +} + +} // net +} // aoo diff --git a/deps/aoo/aoo/src/net/server.hpp b/deps/aoo/aoo/src/net/server.hpp new file mode 100644 index 00000000..7bf0dba4 --- /dev/null +++ b/deps/aoo/aoo/src/net/server.hpp @@ -0,0 +1,324 @@ +/* Copyright (c) 2010-Now Christof Ressi, Winfried Ritsch and others. + * For information on usage and redistribution, and for a DISCLAIMER OF ALL + * WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ + +#pragma once + +#include "aoo/aoo_net.hpp" + +#include "common/sync.hpp" +#include "common/utils.hpp" +#include "common/lockfree.hpp" +#include "common/net_utils.hpp" + +#include "commands.hpp" +#include "../imp.hpp" +#include "SLIP.hpp" + +#include "oscpack/osc/OscOutboundPacketStream.h" +#include "oscpack/osc/OscReceivedElements.h" + +#ifdef _WIN32 +#include +#else +#include +#include +#include +#include +#include +#include +#include +#endif + +#include +#include +#include +#include +#include + +#define DEBUG_THREADS 0 + +namespace aoo { +namespace net { + +class server_imp; + +using ip_address_list = std::vector>; + +struct user; +using user_ptr = std::shared_ptr; +using user_list = std::vector>; + +struct group; +using group_ptr = std::shared_ptr; +using group_list = std::vector>; + + +class client_endpoint { +public: + client_endpoint(server_imp& s, int socket, const ip_address& addr); + + ~client_endpoint(); + + client_endpoint(const client_endpoint&) = delete; + client_endpoint(client_endpoint&&) = delete; + + const ip_address& local_address() const { return addr_; } + + const ip_address_list& public_addresses() const { + return public_addresses_; + } + + bool match(const ip_address& addr) const; + + int socket() const { return socket_; } + + void close(bool notify = true); + + bool active() const { return socket_ >= 0; } + + void send_message(const char *msg, int32_t); + + bool receive_data(); +private: + server_imp *server_; + int socket_; + ip_address_list public_addresses_; + std::shared_ptr user_; + ip_address addr_; + + SLIP> sendbuffer_; + SLIP> recvbuffer_; + + bool handle_message(const char *data, int32_t n); + + bool handle_bundle(const osc::ReceivedBundle& bundle); + + void handle_ping(const osc::ReceivedMessage& msg); + + void handle_login(const osc::ReceivedMessage& msg); + + void handle_group_join(const osc::ReceivedMessage& msg); + + void handle_group_leave(const osc::ReceivedMessage& msg); +}; + +struct user { + user(const std::string& _name, const std::string& _pwd, + int32_t _id, uint32_t _version) + : name(_name), password(_pwd), id(_id), version(_version) {} + + ~user() { LOG_VERBOSE("removed user " << name); } + + bool active() const { return endpoint_ != nullptr; } + + void on_close(server_imp& s); + + bool add_group(std::shared_ptr grp); + + bool remove_group(const group& grp); + + int32_t num_groups() const { return groups_.size(); } + + const group_list& groups() { return groups_; } + + client_endpoint * endpoint() { + return endpoint_; + } + + void set_endpoint(client_endpoint *ep){ + endpoint_ = ep; + } + + // data + const std::string name; + const std::string password; + const int32_t id; + const uint32_t version; + bool legacy = false; +private: + group_list groups_; + client_endpoint *endpoint_ = nullptr; +}; + +struct group { + group(const std::string& _name, const std::string& _pwd) + : name(_name), password(_pwd){} + ~group() { LOG_VERBOSE("removed group " << name); } + + bool add_user(std::shared_ptr usr); + + bool remove_user(const user& usr); + + int32_t num_users() const { return users_.size(); } + + const user_list& users() { return users_; } + + // data + const std::string name; + const std::string password; +private: + user_list users_; +}; + +class server_imp; + +class udp_server { +public: + udp_server(int socket); + ~udp_server(); +private: + int socket_; + ip_address::ip_type type_; + + std::thread receivethread_; + std::thread workerthread_; + + struct udp_packet { + std::vector data; + ip_address address; + }; + using packet_queue = lockfree::unbounded_mpsc_queue>; + packet_queue recvbuffer_; +#if DEBUG_THREADS + std::atomic recvbufferfill_{0}; +#endif + std::atomic quit_{false}; + sync::event event_; + + void receive_packets(); + + void handle_packets(); + + void handle_packet(const char *data, int32_t n, const ip_address& addr); + + void handle_message(const osc::ReceivedMessage& msg, int onset, const ip_address& addr); + + void handle_relay_message(const osc::ReceivedMessage& msg, const ip_address& src); + + void send_message(const char *data, int32_t n, const ip_address& addr); +}; + +class server_imp final : public server { +public: + enum class error { + none, + wrong_password, + permission_denied, + access_denied + }; + + static std::string error_to_string(error e); + + struct ievent { + virtual ~ievent(){} + + union { + aoo_event event_; + aoo_net_error_event error_event_; + aoo_net_user_event user_event_; + aoo_net_group_event group_event_; + }; + }; + + server_imp(int tcpsocket, int udpsocket); + + ~server_imp(); + + ip_address::ip_type type() const { return type_; } + + aoo_error run() override; + + aoo_error quit() override; + + aoo_error set_eventhandler(aoo_eventhandler fn, void *user, int32_t mode) override; + + aoo_bool events_available() override; + + aoo_error poll_events() override; + + aoo_error control(int32_t ctl, intptr_t index, void *ptr, size_t size) override; + + std::shared_ptr get_user(const std::string& name, + const std::string& pwd, + uint32_t version, error& e); + + std::shared_ptr find_user(const std::string& name); + + std::shared_ptr get_group(const std::string& name, + const std::string& pwd, error& e); + + std::shared_ptr find_group(const std::string& name); + + void on_user_joined(user& usr); + + void on_user_left(user& usr); + + void on_user_joined_group(user& usr, group& grp); + + void on_user_left_group(user& usr, group& grp); + + void handle_relay_message(const osc::ReceivedMessage& msg, + const ip_address& src); + + uint32_t flags() const; +private: + int tcpsocket_; + int eventsocket_; + ip_address::ip_type type_; + std::vector pollarray_; + udp_server udpserver_; + // clients + std::list clients_; + // users/groups + int32_t next_user_id_ = 0; + user_list users_; + group_list groups_; + // events + using ievent_ptr = std::unique_ptr; + using event_queue = lockfree::unbounded_mpsc_queue>; + event_queue events_; + aoo_eventhandler eventhandler_ = nullptr; + void *eventcontext_ = nullptr; + aoo_event_mode eventmode_ = AOO_EVENT_NONE; + + void send_event(std::unique_ptr e); + + int32_t get_next_user_id(); + + // signal + std::atomic quit_{false}; + + // options + std::atomic allow_relay_{AOO_NET_RELAY_ENABLE}; + std::atomic notify_on_shutdown_{AOO_NET_NOTIFY_ON_SHUTDOWN}; + + bool receive(); + + void update(); + + /*/////////////////// events //////////////////////*/ + + struct error_event : ievent + { + error_event(int32_t type, int32_t code, const char * msg = 0); + ~error_event(); + }; + + struct user_event : ievent + { + user_event(int32_t type, const char *name, int32_t id, + const ip_address& address); + ~user_event(); + }; + + struct group_event : ievent + { + group_event(int32_t type, const char *group, + const char *user, int32_t id); + ~group_event(); + }; +}; + +} // net +} // aoo diff --git a/deps/aoo/aoo/src/resampler.cpp b/deps/aoo/aoo/src/resampler.cpp new file mode 100644 index 00000000..c03b7b1a --- /dev/null +++ b/deps/aoo/aoo/src/resampler.cpp @@ -0,0 +1,150 @@ +#include "resampler.hpp" + +#include + +namespace aoo { + +// extra space for samplerate fluctuations and non-pow-of-2 blocksizes. +// must be larger than 2! +#define AOO_RESAMPLER_SPACE 2.5 + +void dynamic_resampler::setup(int32_t nfrom, int32_t nto, + int32_t srfrom, int32_t srto, + int32_t nchannels){ + reset(); + nchannels_ = nchannels; + ideal_ratio_ = (double)srto / (double)srfrom; + int32_t blocksize; + if (ideal_ratio_ < 1.0){ + // downsampling + blocksize = std::max(nfrom, (double)nto / ideal_ratio_ + 0.5); + } else { + blocksize = std::max(nfrom, nto); + } + blocksize *= AOO_RESAMPLER_SPACE; +#if AOO_DEBUG_RESAMLER + DO_LOG_DEBUG("resampler setup: nfrom: " << nfrom << ", srfrom: " << srfrom + << ", nto: " << nto << ", srto: " << srto << ", capacity: " << blocksize); +#endif + buffer_.resize(blocksize * nchannels); + update(srfrom, srto); +} + +void dynamic_resampler::reset(){ + // don't touch ratio_! + rdpos_ = 0; + wrpos_ = 0; + balance_ = 0; +} + +void dynamic_resampler::update(double srfrom, double srto){ + if (srfrom == srto){ + ratio_ = 1; + } else { + ratio_ = srto / srfrom; + } +#if AOO_DEBUG_RESAMLER + DO_LOG_DEBUG("srfrom: " << srfrom << ", srto: " << srto << ", ratio: " << ratio_); + DO_LOG_DEBUG("balance: " << balance_ << ", capacity: " << buffer_.size()); +#endif +} + +bool dynamic_resampler::write(const aoo_sample *data, int32_t n){ + if ((buffer_.size() - balance_) < n){ + return false; + } + auto buf = buffer_.data(); + auto size = (int32_t)buffer_.size(); + auto endpos = wrpos_ + n; + if (endpos > size){ + auto split = size - wrpos_; + std::copy(data, data + split, buf + wrpos_); + std::copy(data + split, data + n, buf); + } else { + std::copy(data, data + n, buf + wrpos_); + } + wrpos_ = endpos; + if (wrpos_ >= size){ + wrpos_ -= size; + } + balance_ += n; + return true; +} + +bool dynamic_resampler::read(aoo_sample *data, int32_t n){ + auto size = (int32_t)buffer_.size(); + auto limit = size / nchannels_; + int32_t intpos = (int32_t)rdpos_; + double advance = 1.0 / ratio_; + int32_t intadvance = (int32_t)advance; + if ((advance - intadvance) == 0.0 && (rdpos_ - intpos) == 0.0){ + // non-interpolating (faster) versions + if ((int32_t)balance_ < n * intadvance){ + return false; + } + if (intadvance == 1){ + // just copy samples + int32_t pos = intpos * nchannels_; + int32_t end = pos + n; + auto buf = buffer_.data(); + if (end > size){ + auto n1 = size - pos; + auto n2 = end - size; + std::copy(buf + pos, buf + size, data); + std::copy(buf, buf + n2, data + n1); + } else { + std::copy(buf + pos, buf + end, data); + } + pos += n; + if (pos >= size){ + pos -= size; + } + rdpos_ = pos / nchannels_; + balance_ -= n; + } else { + // skip samples + int32_t pos = rdpos_; + for (int i = 0; i < n; i += nchannels_){ + for (int j = 0; j < nchannels_; ++j){ + int32_t index = pos * nchannels_ + j; + data[i + j] = buffer_[index]; + } + pos += intadvance; + if (pos >= limit){ + pos -= limit; + } + } + rdpos_ = pos; + balance_ -= n * intadvance; + } + } else { + // interpolating version + if (static_cast(balance_ * ratio_ / nchannels_) * nchannels_ <= n){ + return false; + } + double pos = rdpos_; + for (int i = 0; i < n; i += nchannels_){ + int32_t index = (int32_t)pos; + double fract = pos - (double)index; + for (int j = 0; j < nchannels_; ++j){ + int32_t idx1 = index * nchannels_ + j; + int32_t idx2 = (index + 1) * nchannels_ + j; + if (idx2 >= size){ + idx2 -= size; + } + double a = buffer_[idx1]; + double b = buffer_[idx2]; + data[i + j] = a + (b - a) * fract; + } + pos += advance; + if (pos >= limit){ + pos -= limit; + } + } + rdpos_ = pos; + balance_ -= n * advance; + } + return true; +} + +} // aoo diff --git a/deps/aoo/aoo/src/resampler.hpp b/deps/aoo/aoo/src/resampler.hpp new file mode 100644 index 00000000..6231b7c7 --- /dev/null +++ b/deps/aoo/aoo/src/resampler.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "aoo/aoo_types.h" + +#include "imp.hpp" + +#include + +namespace aoo { + +class dynamic_resampler { +public: + void setup(int32_t nfrom, int32_t nto, + int32_t srfrom, int32_t srto, + int32_t nchannels); + void reset(); + void update(double srfrom, double srto); + + bool write(const aoo_sample* data, int32_t n); + bool read(aoo_sample* data, int32_t n); + + int32_t size() const { return balance_; } + int32_t capacity() const { return buffer_.size(); } + double ratio() const { return ideal_ratio_; } +private: + std::vector> buffer_; + int32_t nchannels_ = 0; + double rdpos_ = 0; + int32_t wrpos_ = 0; + double balance_ = 0; + double ratio_ = 1.0; + double ideal_ratio_ = 1.0; +}; + +} diff --git a/deps/aoo/aoo/src/sink.cpp b/deps/aoo/aoo/src/sink.cpp new file mode 100644 index 00000000..35426700 --- /dev/null +++ b/deps/aoo/aoo/src/sink.cpp @@ -0,0 +1,2084 @@ +/* Copyright (c) 2010-Now Christof Ressi, Winfried Ritsch and others. + * For information on usage and redistribution, and for a DISCLAIMER OF ALL + * WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ + +#include "sink.hpp" + +#include +#include + +/*//////////////////// aoo_sink /////////////////////*/ + +aoo_sink * aoo_sink_new(aoo_id id, uint32_t flags) { + return aoo::construct(id, flags); +} + +aoo::sink_imp::sink_imp(aoo_id id, uint32_t flags) + : id_(id) { + eventqueue_.reserve(AOO_EVENTQUEUESIZE); +} + +void aoo_sink_free(aoo_sink *sink) { + // cast to correct type because base class + // has no virtual destructor! + aoo::destroy(static_cast(sink)); +} + +aoo::sink_imp::~sink_imp(){} + +aoo_error aoo_sink_setup(aoo_sink *sink, int32_t samplerate, + int32_t blocksize, int32_t nchannels) { + return sink->setup(samplerate, blocksize, nchannels); +} + +aoo_error aoo::sink_imp::setup(int32_t samplerate, + int32_t blocksize, int32_t nchannels){ + if (samplerate > 0 && blocksize > 0 && nchannels > 0) + { + if (samplerate != samplerate_ || blocksize != blocksize_ || + nchannels != nchannels_) + { + nchannels_ = nchannels; + samplerate_ = samplerate; + blocksize_ = blocksize; + + realsr_.store(samplerate); + + reset_sources(); + } + + // always reset timer + time DLL filter + timer_.setup(samplerate_, blocksize_, timer_check_.load()); + + return AOO_OK; + } else { + return AOO_ERROR_UNSPECIFIED; + } +} + +namespace aoo { + +template +T& as(void *p){ + return *reinterpret_cast(p); +} + +} // aoo + +#define CHECKARG(type) assert(size == sizeof(type)) + +#define GETSOURCEARG \ + source_lock lock(sources_); \ + auto src = get_source_arg(index); \ + if (!src) { \ + return AOO_ERROR_UNSPECIFIED; \ + } \ + +aoo_error aoo_sink_ctl(aoo_sink *sink, int32_t ctl, + intptr_t index, void *p, size_t size) +{ + return sink->control(ctl, index, p, size); +} + +aoo_error aoo::sink_imp::control(int32_t ctl, intptr_t index, + void *ptr, size_t size) +{ + switch (ctl){ + // invite source + case AOO_CTL_INVITE_SOURCE: + { + auto ep = (const aoo_endpoint *)index; + if (!ep){ + return AOO_ERROR_UNSPECIFIED; + } + ip_address addr((const sockaddr *)ep->address, ep->addrlen); + + uint32_t flags = 0; + if (ptr != nullptr) { + CHECKARG(uint32_t); + flags = as(ptr); + } + + push_request(source_request { request_type::invite, addr, ep->id, flags }); + + break; + } + // uninvite source(s) + case AOO_CTL_UNINVITE_SOURCE: + { + auto ep = (const aoo_endpoint *)index; + if (ep){ + // single source + ip_address addr((const sockaddr *)ep->address, ep->addrlen); + + push_request(source_request { request_type::uninvite, addr, ep->id }); + } else { + // all sources + push_request(source_request { request_type::uninvite_all }); + } + break; + } + // id + case AOO_CTL_SET_ID: + { + CHECKARG(int32_t); + auto newid = as(ptr); + if (id_.exchange(newid) != newid){ + // LATER clear source list here + } + break; + } + case AOO_CTL_GET_ID: + CHECKARG(aoo_id); + as(ptr) = id(); + break; + // reset + case AOO_CTL_RESET: + { + if (index != 0){ + GETSOURCEARG; + src->reset(*this); + } else { + // reset all sources + reset_sources(); + // reset time DLL + timer_.reset(); + } + break; + } + // request format + case AOO_CTL_REQUEST_FORMAT: + { + CHECKARG(aoo_format); + GETSOURCEARG; + return src->request_format(*this, as(ptr)); + } + // get format + case AOO_CTL_GET_FORMAT: + { + assert(size >= sizeof(aoo_format)); + GETSOURCEARG; + return src->get_format(as(ptr), size); + } + // buffer size + case AOO_CTL_SET_BUFFERSIZE: + { + CHECKARG(int32_t); + auto bufsize = std::max(0, as(ptr)); + if (bufsize != buffersize_){ + buffersize_.store(bufsize); + reset_sources(); + } + break; + } + case AOO_CTL_GET_BUFFERSIZE: + CHECKARG(int32_t); + as(ptr) = buffersize_.load(); + break; + // get buffer fill ratio + case AOO_CTL_GET_BUFFER_FILL_RATIO: + { + CHECKARG(float); + GETSOURCEARG; + as(ptr) = src->get_buffer_fill_ratio(); + break; + } + // timer check + case AOO_CTL_SET_TIMER_CHECK: + CHECKARG(aoo_bool); + timer_check_.store(as(ptr)); + break; + case AOO_CTL_GET_TIMER_CHECK: + CHECKARG(aoo_bool); + as(ptr) = timer_check_.load(); + break; + // dynamic resampling + case AOO_CTL_SET_DYNAMIC_RESAMPLING: + CHECKARG(aoo_bool); + dynamic_resampling_.store(as(ptr)); + timer_.reset(); // ! + break; + case AOO_CTL_GET_DYNAMIC_RESAMPLING: + CHECKARG(aoo_bool); + as(ptr) = dynamic_resampling_.load(); + break; + // time DLL filter bandwidth + case AOO_CTL_SET_DLL_BANDWIDTH: + { + CHECKARG(float); + auto bw = std::max(0, std::min(1, as(ptr))); + dll_bandwidth_.store(bw); + timer_.reset(); // will update time DLL and reset timer + break; + } + case AOO_CTL_GET_DLL_BANDWIDTH: + CHECKARG(float); + as(ptr) = dll_bandwidth_.load(); + break; + // real samplerate + case AOO_CTL_GET_REAL_SAMPLERATE: + CHECKARG(double); + as(ptr) = realsr_.load(std::memory_order_relaxed); + break; + // packetsize + case AOO_CTL_SET_PACKETSIZE: + { + CHECKARG(int32_t); + const int32_t minpacketsize = 64; + auto packetsize = as(ptr); + if (packetsize < minpacketsize){ + LOG_WARNING("packet size too small! setting to " << minpacketsize); + packetsize_.store(minpacketsize); + } else if (packetsize > AOO_MAXPACKETSIZE){ + LOG_WARNING("packet size too large! setting to " << AOO_MAXPACKETSIZE); + packetsize_.store(AOO_MAXPACKETSIZE); + } else { + packetsize_.store(packetsize); + } + break; + } + case AOO_CTL_GET_PACKETSIZE: + CHECKARG(int32_t); + as(ptr) = packetsize_.load(); + break; + // resend data + case AOO_CTL_SET_RESEND_DATA: + CHECKARG(aoo_bool); + resend_.store(as(ptr)); + break; + case AOO_CTL_GET_RESEND_DATA: + CHECKARG(aoo_bool); + as(ptr) = resend_.load(); + break; + // resend interval + case AOO_CTL_SET_RESEND_INTERVAL: + { + CHECKARG(int32_t); + auto interval = std::max(0, as(ptr)) * 0.001; + resend_interval_.store(interval); + break; + } + case AOO_CTL_GET_RESEND_INTERVAL: + CHECKARG(int32_t); + as(ptr) = resend_interval_.load() * 1000.0; + break; + // resend limit + case AOO_CTL_SET_RESEND_LIMIT: + { + CHECKARG(int32_t); + auto limit = std::max(1, as(ptr)); + resend_limit_.store(limit); + break; + } + case AOO_CTL_GET_RESEND_LIMIT: + CHECKARG(int32_t); + as(ptr) = resend_limit_.load(); + break; + // source timeout + case AOO_CTL_SET_SOURCE_TIMEOUT: + { + CHECKARG(int32_t); + auto timeout = std::max(0, as(ptr)) * 0.001; + source_timeout_.store(timeout); + break; + } + case AOO_CTL_GET_SOURCE_TIMEOUT: + CHECKARG(int32_t); + as(ptr) = source_timeout_.load() * 1000.0; + break; + // unknown + default: + LOG_WARNING("aoo_sink: unsupported control " << ctl); + return AOO_ERROR_UNSPECIFIED; + } + return AOO_OK; +} + +aoo_error aoo_sink_handle_message(aoo_sink *sink, const char *data, int32_t n, + const void *address, int32_t addrlen) { + return sink->handle_message(data, n, address, addrlen); +} + +aoo_error aoo::sink_imp::handle_message(const char *data, int32_t n, + const void *address, int32_t addrlen) { + if (samplerate_ == 0){ + return AOO_ERROR_UNSPECIFIED; // not setup yet + } + + aoo_type type; + aoo_id sinkid; + int32_t onset; + auto err = aoo_parse_pattern(data, n, &type, &sinkid, &onset); + if (err != AOO_OK){ + LOG_WARNING("not an AoO message!"); + return AOO_ERROR_UNSPECIFIED; + } + + if (type != AOO_TYPE_SINK){ + LOG_WARNING("not a sink message!"); + return AOO_ERROR_UNSPECIFIED; + } + if (sinkid != id()){ + LOG_WARNING("wrong sink ID!"); + return AOO_ERROR_UNSPECIFIED; + } + + ip_address addr((const sockaddr *)address, addrlen); + + if (data[0] == 0){ + // binary message + auto cmd = aoo::from_bytes(data + AOO_BIN_MSG_DOMAIN_SIZE + 2); + switch (cmd){ + case AOO_BIN_MSG_CMD_DATA: + return handle_data_message(data + onset, n - onset, addr); + default: + return AOO_ERROR_UNSPECIFIED; + } + } else { + // OSC message + try { + osc::ReceivedPacket packet(data, n); + osc::ReceivedMessage msg(packet); + + auto pattern = msg.AddressPattern() + onset; + if (!strcmp(pattern, AOO_MSG_FORMAT)){ + return handle_format_message(msg, addr); + } else if (!strcmp(pattern, AOO_MSG_DATA)){ + return handle_data_message(msg, addr); + } else if (!strcmp(pattern, AOO_MSG_PING)){ + return handle_ping_message(msg, addr); + } else { + LOG_WARNING("unknown message " << pattern); + } + } catch (const osc::Exception& e){ + LOG_ERROR("aoo_sink: exception in handle_message: " << e.what()); + } + return AOO_ERROR_UNSPECIFIED; + } +} + +aoo_error aoo_sink_send(aoo_sink *sink, aoo_sendfn fn, void *user){ + return sink->send(fn, user); +} + +aoo_error aoo::sink_imp::send(aoo_sendfn fn, void *user){ + sendfn reply(fn, user); + + // handle requests + source_request r; + while (requestqueue_.try_pop(r)){ + switch (r.type) { + case request_type::invite: + { + // try to find existing source + // we might want to invite an existing source, + // e.g. when it is currently uninviting + // NOTE that sources can also be added in the network + // receive thread (see handle_data() or handle_format()), + // so we have to lock a mutex to avoid the ABA problem. + sync::scoped_lock lock1(source_mutex_); + source_lock lock2(sources_); + auto src = find_source(r.address, r.id); + if (!src){ + src = add_source(r.address, r.id, r.flags); + } + src->invite(*this); + break; + } + case request_type::uninvite: + { + // try to find existing source + source_lock lock(sources_); + auto src = find_source(r.address, r.id); + if (src){ + src->uninvite(*this); + } else { + LOG_WARNING("aoo: can't uninvite - source not found"); + } + break; + } + case request_type::uninvite_all: + { + source_lock lock(sources_); + for (auto& src : sources_){ + src.uninvite(*this); + } + break; + } + default: + break; + } + } + + source_lock lock(sources_); + for (auto& s : sources_){ + s.send(*this, reply); + } + lock.unlock(); + + // free unused source_descs + if (!sources_.try_free()){ + // LOG_DEBUG("aoo::sink: try_free() would block"); + } + + return AOO_OK; +} + +aoo_error aoo_sink_process(aoo_sink *sink, aoo_sample **data, + int32_t nsamples, uint64_t t) { + return sink->process(data, nsamples, t); +} + +#define AOO_MAXNUMEVENTS 256 + +aoo_error aoo::sink_imp::process(aoo_sample **data, int32_t nsamples, uint64_t t){ + // clear outputs + for (int i = 0; i < nchannels_; ++i){ + std::fill(data[i], data[i] + nsamples, 0); + } + + // update timer + // always do this, even if there are no sources! + bool dynamic_resampling = dynamic_resampling_.load(std::memory_order_relaxed); + double error; + auto state = timer_.update(t, error); + if (state == timer::state::reset){ + LOG_DEBUG("setup time DLL filter for sink"); + auto bw = dll_bandwidth_.load(std::memory_order_relaxed); + dll_.setup(samplerate_, blocksize_, bw, 0); + realsr_.store(samplerate_, std::memory_order_relaxed); + } else if (state == timer::state::error){ + // recover sources + int32_t xrunsamples = error * samplerate_ + 0.5; + + // no lock needed - sources are only removed in this thread! + for (auto& s : sources_){ + s.add_xrun(xrunsamples); + } + + sink_event e(AOO_XRUN_EVENT); + e.count = (float)xrunsamples / (float)blocksize_; + send_event(e, AOO_THREAD_AUDIO); + + timer_.reset(); + } else if (dynamic_resampling) { + // update time DLL, but only if n matches blocksize! + auto elapsed = timer_.get_elapsed(); + if (nsamples == blocksize_){ + dll_.update(elapsed); + #if AOO_DEBUG_DLL + DO_LOG_DEBUG("time elapsed: " << elapsed << ", period: " + << dll_.period() << ", samplerate: " << dll_.samplerate()); + #endif + } else { + // reset time DLL with nominal samplerate + auto bw = dll_bandwidth_.load(std::memory_order_relaxed); + dll_.setup(samplerate_, blocksize_, bw, elapsed); + } + realsr_.store(dll_.samplerate(), std::memory_order_relaxed); + } + + bool didsomething = false; + + // no lock needed - sources are only removed in this thread! + for (auto it = sources_.begin(); it != sources_.end();){ + if (it->process(*this, data, nsamples, t)){ + didsomething = true; + } else if (!it->is_active(*this)){ + // move source to garbage list (will be freed in send()) + if (it->is_inviting()){ + LOG_VERBOSE("aoo::sink: invitation for " << it->address().name() + << " " << it->address().port() << " timed out"); + sink_event e(AOO_INVITE_TIMEOUT_EVENT, *it); + send_event(e, AOO_THREAD_AUDIO); + } else { + LOG_VERBOSE("aoo::sink: removed inactive source " << it->address().name() + << " " << it->address().port()); + sink_event e(AOO_SOURCE_REMOVE_EVENT, *it); + send_event(e, AOO_THREAD_AUDIO); + } + it = sources_.erase(it); + continue; + } + ++it; + } + + if (didsomething){ + #if AOO_CLIP_OUTPUT + for (int i = 0; i < nchannels_; ++i){ + auto chn = data[i]; + for (int j = 0; j < nsamples; ++j){ + if (chn[j] > 1.0){ + chn[j] = 1.0; + } else if (chn[j] < -1.0){ + chn[j] = -1.0; + } + } + } + #endif + } + return AOO_OK; +} + +aoo_error aoo_sink_set_eventhandler(aoo_sink *sink, aoo_eventhandler fn, + void *user, int32_t mode) +{ + return sink->set_eventhandler(fn, user, mode); +} + +aoo_error aoo::sink_imp::set_eventhandler(aoo_eventhandler fn, void *user, int32_t mode) +{ + eventhandler_ = fn; + eventcontext_ = user; + eventmode_ = (aoo_event_mode)mode; + return AOO_OK; +} + +aoo_bool aoo_sink_events_available(aoo_sink *sink){ + return sink->events_available(); +} + +aoo_bool aoo::sink_imp::events_available(){ + if (!eventqueue_.empty()){ + return true; + } + + source_lock lock(sources_); + for (auto& src : sources_){ + if (src.has_events()){ + return true; + } + } + + return false; +} + +aoo_error aoo_sink_poll_events(aoo_sink *sink){ + return sink->poll_events(); +} + +#define EVENT_THROTTLE 1000 + +aoo_error aoo::sink_imp::poll_events(){ + int total = 0; + sink_event e; + while (eventqueue_.try_pop(e)){ + if (e.type == AOO_XRUN_EVENT){ + aoo_xrun_event xe; + xe.type = e.type; + xe.count = e.count; + eventhandler_(eventcontext_, (const aoo_event *)&xe, + AOO_THREAD_UNKNOWN); + } else { + aoo_source_event se; + se.type = e.type; + se.ep.address = e.address.address(); + se.ep.addrlen = e.address.length(); + se.ep.id = e.id; + eventhandler_(eventcontext_, (const aoo_event *)&se, + AOO_THREAD_UNKNOWN); + } + + total++; + } + // we only need to protect against source removal + source_lock lock(sources_); + for (auto& src : sources_){ + total += src.poll_events(*this, eventhandler_, eventcontext_); + if (total > EVENT_THROTTLE){ + break; + } + } + return AOO_OK; +} + +namespace aoo { + +void sink_imp::send_event(const sink_event &e, aoo_thread_level level) { + switch (eventmode_){ + case AOO_EVENT_POLL: + eventqueue_.push(e); + break; + case AOO_EVENT_CALLBACK: + { + aoo_sink_event se; + se.type = e.type; + se.ep.address = e.address.address(); + se.ep.addrlen = e.address.length(); + se.ep.id = e.id; + eventhandler_(eventcontext_, (const aoo_event *)&se, level); + break; + } + default: + break; + } +} + +// only called if mode is AOO_EVENT_CALLBACK +void sink_imp::call_event(const event &e, aoo_thread_level level) const { + eventhandler_(eventcontext_, &e.event_, level); + // some events use dynamic memory + if (e.type_ == AOO_FORMAT_CHANGE_EVENT){ + memory.free(memory_block::from_bytes((void *)e.format.format)); + } +} + +aoo::source_desc * sink_imp::find_source(const ip_address& addr, aoo_id id){ + for (auto& src : sources_){ + if (src.match(addr, id)){ + return &src; + } + } + return nullptr; +} + +aoo::source_desc * sink_imp::get_source_arg(intptr_t index){ + auto ep = (const aoo_endpoint *)index; + if (!ep){ + LOG_ERROR("aoo_sink: missing source argument"); + return nullptr; + } + ip_address addr((const sockaddr *)ep->address, ep->addrlen); + auto src = find_source(addr, ep->id); + if (!src){ + LOG_ERROR("aoo_sink: couldn't find source"); + } + return src; +} + +source_desc * sink_imp::add_source(const ip_address& addr, aoo_id id, uint32_t flags){ + // add new source + sources_.emplace_front(addr, id, elapsed_time(), flags); + return &sources_.front(); +} + +void sink_imp::reset_sources(){ + source_lock lock(sources_); + for (auto& src : sources_){ + src.reset(*this); + } +} + +// /format +aoo_error sink_imp::handle_format_message(const osc::ReceivedMessage& msg, + const ip_address& addr) +{ + auto it = msg.ArgumentsBegin(); + + aoo_id id = (it++)->AsInt32(); + int32_t version = (it++)->AsInt32(); + + // LATER handle this in the source_desc (e.g. ignoring further messages) + if (!check_version(version)){ + LOG_ERROR("aoo_sink: source version not supported"); + return AOO_ERROR_UNSPECIFIED; + } + + int32_t salt = (it++)->AsInt32(); + // get format from arguments + aoo_format f; + f.nchannels = (it++)->AsInt32(); + f.samplerate = (it++)->AsInt32(); + f.blocksize = (it++)->AsInt32(); + f.codec = (it++)->AsString(); + f.size = sizeof(aoo_format); + const void *settings; + osc::osc_bundle_element_size_t size; + (it++)->AsBlob(settings, size); + // for backwards comptability (later remove check) + uint32_t flags = (it != msg.ArgumentsEnd() && it->IsInt32()) ? + (uint32_t)(it++)->AsInt32() : 0; + + if (id < 0){ + LOG_WARNING("bad ID for " << AOO_MSG_FORMAT << " message"); + return AOO_ERROR_UNSPECIFIED; + } + // try to find existing source + // NOTE: sources can also be added in the network send thread, + // so we have to lock a mutex to avoid the ABA problem! + sync::scoped_lock lock1(source_mutex_); + source_lock lock2(sources_); + auto src = find_source(addr, id); + if (!src){ + src = add_source(addr, id); + } + return src->handle_format(*this, salt, f, (const char *)settings, size, flags); +} + +aoo_error sink_imp::handle_data_message(const osc::ReceivedMessage& msg, + const ip_address& addr) +{ + auto it = msg.ArgumentsBegin(); + + auto id = (it++)->AsInt32(); + + aoo::net_packet d; + d.salt = (it++)->AsInt32(); + d.sequence = (it++)->AsInt32(); + d.samplerate = (it++)->AsDouble(); + d.channel = (it++)->AsInt32(); + d.totalsize = (it++)->AsInt32(); + d.nframes = (it++)->AsInt32(); + d.frame = (it++)->AsInt32(); + const void *blobdata; + osc::osc_bundle_element_size_t blobsize; + (it++)->AsBlob(blobdata, blobsize); + d.data = (const char *)blobdata; + d.size = blobsize; + + return handle_data_packet(d, false, addr, id); +} + +// binary data message: +// id (int32), salt (int32), seq (int32), channel (int16), flags (int16), +// [total (int32), nframes (int16), frame (int16)], [sr (float64)], +// size (int32), data... + +aoo_error sink_imp::handle_data_message(const char *msg, int32_t n, + const ip_address& addr) +{ + // check size (excluding samplerate, frames and data) + if (n < 20){ + LOG_ERROR("handle_data_message: header too small!"); + return AOO_ERROR_UNSPECIFIED; + } + + auto it = msg; + + auto id = aoo::read_bytes(it); + + aoo::net_packet d; + d.salt = aoo::read_bytes(it); + d.sequence = aoo::read_bytes(it); + d.channel = aoo::read_bytes(it); + auto flags = aoo::read_bytes(it); + if (flags & AOO_BIN_MSG_DATA_FRAMES){ + d.totalsize = aoo::read_bytes(it); + d.nframes = aoo::read_bytes(it); + d.frame = aoo::read_bytes(it); + } else { + d.totalsize = 0; + d.nframes = 1; + d.frame = 0; + } + if (flags & AOO_BIN_MSG_DATA_SAMPLERATE){ + d.samplerate = aoo::read_bytes(it); + } else { + d.samplerate = 0; + } + + d.size = aoo::read_bytes(it); + if (d.totalsize == 0){ + d.totalsize = d.size; + } + + if (n < ((it - msg) + d.size)){ + LOG_ERROR("handle_data_bin_message: wrong data size!"); + return AOO_ERROR_UNSPECIFIED; + } + + d.data = it; + + return handle_data_packet(d, true, addr, id); +} + +aoo_error sink_imp::handle_data_packet(net_packet& d, bool binary, + const ip_address& addr, aoo_id id) +{ + if (id < 0){ + LOG_WARNING("bad ID for " << AOO_MSG_DATA << " message"); + return AOO_ERROR_UNSPECIFIED; + } + // try to find existing source + // NOTE: sources can also be added in the network send thread, + // so we have to lock a mutex to avoid the ABA problem! + sync::scoped_lock lock1(source_mutex_); + source_lock lock2(sources_); + auto src = find_source(addr, id); + if (!src){ + src = add_source(addr, id); + } + return src->handle_data(*this, d, binary); +} + +aoo_error sink_imp::handle_ping_message(const osc::ReceivedMessage& msg, + const ip_address& addr) +{ + auto it = msg.ArgumentsBegin(); + + auto id = (it++)->AsInt32(); + time_tag tt = (it++)->AsTimeTag(); + + if (id < 0){ + LOG_WARNING("bad ID for " << AOO_MSG_PING << " message"); + return AOO_ERROR_UNSPECIFIED; + } + // try to find existing source + source_lock lock(sources_); + auto src = find_source(addr, id); + if (src){ + return src->handle_ping(*this, tt); + } else { + LOG_WARNING("couldn't find source " << id << " for " << AOO_MSG_PING << " message"); + return AOO_ERROR_UNSPECIFIED; + } +} + +/*////////////////////////// event ///////////////////////////////////*/ + +// 'event' is always used inside 'source_desc', so we can safely +// store a pointer to the sockaddr. the ip_address itself +// never changes during lifetime of the 'source_desc'! +// NOTE: this assumes that the event queue is polled regularly, +// i.e. before a source_desc can be possibly autoremoved. +event::event(aoo_event_type type, const source_desc& desc){ + source.type = type; + source.ep.address = desc.address().address(); + source.ep.addrlen = desc.address().length(); + source.ep.id = desc.id(); +} + +// 'sink_event' is used in 'sink' for source events that can outlive +// its corresponding 'source_desc', therefore the ip_address is copied! +sink_event::sink_event(aoo_event_type _type, const source_desc &desc) + : type(_type), address(desc.address()), id(desc.id()) {} + +/*////////////////////////// source_desc /////////////////////////////*/ + +source_desc::source_desc(const ip_address& addr, aoo_id id, double time, uint32_t flags) + : addr_(addr), id_(id), flags_(flags), last_packet_time_(time) +{ + // reserve some memory, so we don't have to allocate memory + // when pushing events in the audio thread. + eventqueue_.reserve(AOO_EVENTQUEUESIZE); + // resendqueue_.reserve(256); + LOG_DEBUG("source_desc"); +} + +source_desc::~source_desc(){ + // flush event queue + event e; + while (eventqueue_.try_pop(e)){ + if (e.type_ == AOO_FORMAT_CHANGE_EVENT){ + auto mem = memory_block::from_bytes((void *)e.format.format); + memory_block::free(mem); + } + } + // flush packet queue + net_packet d; + while (packetqueue_.try_pop(d)){ + auto mem = memory_block::from_bytes((void *)d.data); + memory_block::free(mem); + } + LOG_DEBUG("~source_desc"); +} + +bool source_desc::is_active(const sink_imp& s) const { + auto last = last_packet_time_.load(std::memory_order_relaxed); + return (s.elapsed_time() - last) < s.source_timeout(); +} + +aoo_error source_desc::get_format(aoo_format &format, size_t size){ + // synchronize with handle_format() and update()! + scoped_shared_lock lock(mutex_); + if (decoder_){ + return decoder_->get_format(format, size); + } else { + return AOO_ERROR_UNSPECIFIED; + } +} + +void source_desc::reset(const sink_imp& s){ + // take writer lock! + scoped_lock lock(mutex_); + update(s); +} + +#define MAXHWBUFSIZE 2048 +#define MINSAMPLERATE 44100 + +void source_desc::update(const sink_imp& s){ + // resize audio ring buffer + if (decoder_ && decoder_->blocksize() > 0 && decoder_->samplerate() > 0){ + // recalculate buffersize from ms to samples + int32_t bufsize = (double)s.buffersize() * 0.001 * decoder_->samplerate(); + // number of buffers (round up!) + int32_t nbuffers = std::ceil((double)bufsize / (double)decoder_->blocksize()); + // minimum buffer size depends on resampling and reblocking! + auto downsample = (double)decoder_->samplerate() / (double)s.samplerate(); + auto reblock = (double)s.blocksize() / (double)decoder_->blocksize(); + minblocks_ = std::ceil(downsample * reblock); + nbuffers = std::max(nbuffers, minblocks_); + int32_t aqbuffers = nbuffers; // minblocks_; // force + + LOG_DEBUG("source_desc: buffersize (ms): " << s.buffersize() + << ", samples: " << bufsize << ", nbuffers: " << nbuffers + << ", minimum: " << minblocks_); + + #if 0 + // don't touch the event queue once constructed + eventqueue_.reset(); + #endif + + auto nsamples = decoder_->nchannels() * decoder_->blocksize(); + double sr = decoder_->samplerate(); // nominal samplerate + + // setup audio buffer + auto nbytes = sizeof(block_data::header) + nsamples * sizeof(aoo_sample); + // align to 8 bytes + nbytes = (nbytes + 7) & ~7; + audioqueue_.resize(nbytes, aqbuffers); + // fill buffer + for (int i = 0; i < aqbuffers; ++i){ + auto b = (block_data *)audioqueue_.write_data(); + // push nominal samplerate, channel + silence + b->header.samplerate = sr; + b->header.channel = 0; + std::fill(b->data, b->data + nsamples, 0); + audioqueue_.write_commit(); + } + + // setup resampler + resampler_.setup(decoder_->blocksize(), s.blocksize(), + decoder_->samplerate(), s.samplerate(), + decoder_->nchannels()); + + // setup jitter buffer. + // if we use a very small audio buffer size, we have to make sure that + // we have enough space in the jitter buffer in case the source uses + // a larger hardware buffer size and consequently sends packets in batches. + // we don't know the actual source samplerate and hardware buffer size, + // so we have to make a pessimistic guess. + auto hwsamples = (double)decoder_->samplerate() / MINSAMPLERATE * MAXHWBUFSIZE; + auto minbuffers = std::ceil(hwsamples / (double)decoder_->blocksize()); + auto jitterbufsize = std::max(nbuffers, minbuffers); + //auto jitterbufsize = nbuffers; + // LATER optimize max. block size + jitterbuffer_.resize(jitterbufsize, nsamples * sizeof(double)); + LOG_DEBUG("jitter buffer: " << jitterbufsize << " blocks"); + + streamstate_ = AOO_STREAM_STATE_INIT; + lost_since_ping_.store(0); + channel_ = 0; + skipblocks_ = 0; + underrun_ = false; + didupdate_ = true; + + // reset decoder to avoid garbage from previous stream + decoder_->reset(); + } +} + +// called from the network thread +void source_desc::invite(const sink_imp& s){ + // only invite when idle or uninviting! + // NOTE: state can only change in this thread (= send thread), + // so we don't need a CAS loop. + auto state = state_.load(std::memory_order_relaxed); + while (state != source_state::stream){ + // special case: (re)invite shortly after uninvite + if (state == source_state::uninvite){ + // update last packet time to reset timeout! + last_packet_time_.store(s.elapsed_time()); + // force new format, otherwise handle_format() would ignore + // the format messages and we would spam the source with + // redundant invitation messages until we time out. + // NOTE: don't use a negative value, otherwise we would get + // a redundant "add" event, see handle_format(). + scoped_lock lock(mutex_); + salt_++; + } + #if 1 + state_time_.store(0.0); // start immediately + #else + state_time_.store(s.elapsed_time()); // wait + #endif + if (state_.compare_exchange_weak(state, source_state::invite)){ + LOG_DEBUG("source_desc: invite"); + return; + } + } + LOG_WARNING("aoo: couldn't invite source - already active"); +} + +// called from the network thread +void source_desc::uninvite(const sink_imp& s){ + // state can change in different threads, so we need a CAS loop + auto state = state_.load(std::memory_order_relaxed); + while (state != source_state::idle){ + // update start time for uninvite phase, see handle_data() + state_time_.store(s.elapsed_time()); + if (state_.compare_exchange_weak(state, source_state::uninvite)){ + LOG_DEBUG("source_desc: uninvite"); + return; + } + } + LOG_WARNING("aoo: couldn't uninvite source - not active"); +} + +aoo_error source_desc::request_format(const sink_imp& s, const aoo_format &f){ + if (state_.load(std::memory_order_relaxed) == source_state::uninvite){ + // requesting a format during uninvite doesn't make sense. + // also, we couldn't use 'state_time', because it has a different + // meaning during the uninvite phase. + return AOO_ERROR_UNSPECIFIED; + } + + if (!aoo::find_codec(f.codec)){ + LOG_WARNING("request_format: codec '" << f.codec << "' not supported"); + return AOO_ERROR_UNSPECIFIED; + } + + // copy format + auto fmt = (aoo_format *)aoo::allocate(f.size); + memcpy(fmt, &f, f.size); + + LOG_DEBUG("source_desc: request format"); + + scoped_lock lock(mutex_); // writer lock! + + format_request_.reset(fmt); + + format_time_ = s.elapsed_time(); +#if 1 + state_time_.store(0.0); // start immediately +#else + state_time_.store(s.elapsed_time()); // wait +#endif + + return AOO_OK; +} + +float source_desc::get_buffer_fill_ratio(){ + scoped_shared_lock lock(mutex_); + if (decoder_){ + // consider samples in resampler! + auto nsamples = decoder_->nchannels() * decoder_->blocksize(); + auto available = (double)audioqueue_.read_available() + + (double)resampler_.size() / (double)nsamples; + auto ratio = (available + jitterbuffer_.size()) / (double)(audioqueue_.capacity() + jitterbuffer_.capacity()); + //LOG_DEBUG("fill ratio: " << ratio << ", audioqueue: " << audioqueue_.read_available() + // << ", resampler: " << (double)resampler_.size() / (double)nsamples << " jitter: " << jitterbuffer_.size()); + + // FIXME sometimes the result is bigger than 1.0 + return std::min(1.0, ratio); + } else { + return 0.0; + } +} + +// /aoo/sink//format + +aoo_error source_desc::handle_format(const sink_imp& s, int32_t salt, const aoo_format& f, + const char *settings, int32_t size, uint32_t flags){ + LOG_DEBUG("handle_format"); + // ignore redundant format messages! + // NOTE: salt_ can only change in this thread, + // so we don't need a lock to safely *read* it! + if (salt == salt_){ + return AOO_ERROR_UNSPECIFIED; + } + + // look up codec + auto c = aoo::find_codec(f.codec); + if (!c){ + LOG_ERROR("codec '" << f.codec << "' not supported!"); + return AOO_ERROR_UNSPECIFIED; + } + + // try to deserialize format + aoo_format_storage fmt; + if (c->deserialize(f, settings, size, + fmt.header, sizeof(fmt)) != AOO_OK){ + return AOO_ERROR_UNSPECIFIED; + } + + // Create a new decoder if necessary. + // This is the only thread where the decoder can possibly + // change, so we don't need a lock to safely *read* it! + std::unique_ptr new_decoder; + bool changed = false; + + if (!decoder_ || strcmp(decoder_->name(), f.codec)){ + new_decoder = c->create_decoder(); + if (!new_decoder){ + LOG_ERROR("couldn't create decoder!"); + return AOO_ERROR_UNSPECIFIED; + } + changed = true; + } else { + changed = !decoder_->compare(fmt.header); // thread-safe + } + + unique_lock lock(mutex_); // writer lock! + if (new_decoder){ + decoder_ = std::move(new_decoder); + } + + auto oldsalt = salt_; + salt_ = salt; + flags_ = flags; + format_request_ = nullptr; + + // set format (if changed) + if (changed && decoder_->set_format(fmt.header) != AOO_OK){ + return AOO_ERROR_UNSPECIFIED; + } + + // always update! + update(s); + + lock.unlock(); + + // NOTE: state can be changed in both network threads, + // so we need a CAS loop. + auto state = state_.load(std::memory_order_relaxed); + while (state == source_state::idle || state == source_state::invite){ + if (state_.compare_exchange_weak(state, source_state::stream)){ + // only push "add" event, if this is the first format message! + if (oldsalt < 0){ + event e(AOO_SOURCE_ADD_EVENT, *this); + send_event(s, e, AOO_THREAD_AUDIO); + LOG_DEBUG("add new source with id " << id()); + } + break; + } + } + + // send format event (if changed) + // NOTE: we could just allocate 'aoo_format_storage', but it would be wasteful. + if (changed){ + auto mem = s.memory.alloc(fmt.header.size); + memcpy(mem->data(), &fmt, fmt.header.size); + + event e(AOO_FORMAT_CHANGE_EVENT, *this); + e.format.format = (const aoo_format *)mem->data(); + + send_event(s, e, AOO_THREAD_NETWORK); + } + + return AOO_OK; +} + +// /aoo/sink//data + +aoo_error source_desc::handle_data(const sink_imp& s, net_packet& d, bool binary) +{ + binary_.store(binary, std::memory_order_relaxed); + + // always update packet time to signify that we're receiving packets + last_packet_time_.store(s.elapsed_time(), std::memory_order_relaxed); + + // if we're in uninvite state, ignore data and send uninvite request. + if (state_.load(std::memory_order_acquire) == source_state::uninvite){ + // only try for a certain amount of time to avoid spamming the source. + auto delta = s.elapsed_time() - state_time_.load(std::memory_order_relaxed); + if (delta < s.source_timeout()){ + push_request(request(request_type::uninvite)); + } + // ignore data message + return AOO_OK; + } + + // the source format might have changed and we haven't noticed, + // e.g. because of dropped UDP packets. + // NOTE: salt_ can only change in this thread! + if (d.salt != salt_){ + push_request(request(request_type::format)); + return AOO_OK; + } + + // synchronize with update()! + scoped_shared_lock lock(mutex_); + +#if 1 + if (!decoder_){ + LOG_DEBUG("ignore data message"); + return AOO_ERROR_UNSPECIFIED; + } +#else + assert(decoder_ != nullptr); +#endif + // check and fix up samplerate + if (d.samplerate == 0){ + // no dynamic resampling, just use nominal samplerate + d.samplerate = decoder_->samplerate(); + } + + // copy blob data and push to queue + auto data = (char *)s.memory.alloc(d.size)->data(); + memcpy(data, d.data, d.size); + d.data = data; + + packetqueue_.push(d); + +#if AOO_DEBUG_DATA + LOG_DEBUG("got block: seq = " << d.sequence << ", sr = " << d.samplerate + << ", chn = " << d.channel << ", totalsize = " << d.totalsize + << ", nframes = " << d.nframes << ", frame = " << d.frame << ", size " << d.size); +#endif + + return AOO_OK; +} + +// /aoo/sink//ping