diff --git a/audio-mixer/CMakeLists.txt b/audio-mixer/CMakeLists.txt index eac2792883..7e83e6fc1a 100644 --- a/audio-mixer/CMakeLists.txt +++ b/audio-mixer/CMakeLists.txt @@ -18,4 +18,10 @@ include_glm(${TARGET_NAME} ${ROOT_DIR}) # link the shared hifi library include(${MACRO_DIR}/LinkHifiLibrary.cmake) link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR}) -link_hifi_library(audio ${TARGET_NAME} ${ROOT_DIR}) \ No newline at end of file +link_hifi_library(audio ${TARGET_NAME} ${ROOT_DIR}) + +# link the stk library +set(STK_ROOT_DIR ${ROOT_DIR}/externals/stk) +find_package(STK REQUIRED) +target_link_libraries(${TARGET_NAME} ${STK_LIBRARIES}) +include_directories(${STK_INCLUDE_DIRS}) \ No newline at end of file diff --git a/audio-mixer/src/AvatarAudioRingBuffer.cpp b/audio-mixer/src/AvatarAudioRingBuffer.cpp index 6da4e20871..144f0181d9 100644 --- a/audio-mixer/src/AvatarAudioRingBuffer.cpp +++ b/audio-mixer/src/AvatarAudioRingBuffer.cpp @@ -11,10 +11,18 @@ #include "AvatarAudioRingBuffer.h" AvatarAudioRingBuffer::AvatarAudioRingBuffer() : + _twoPoles(), _shouldLoopbackForAgent(false) { } +AvatarAudioRingBuffer::~AvatarAudioRingBuffer() { + // enumerate the freeVerbs map and delete the FreeVerb objects + for (TwoPoleAgentMap::iterator poleIterator = _twoPoles.begin(); poleIterator != _twoPoles.end(); poleIterator++) { + delete poleIterator->second; + } +} + int AvatarAudioRingBuffer::parseData(unsigned char* sourceBuffer, int numBytes) { _shouldLoopbackForAgent = (sourceBuffer[0] == PACKET_HEADER_MICROPHONE_AUDIO_WITH_ECHO); return PositionalAudioRingBuffer::parseData(sourceBuffer, numBytes); diff --git a/audio-mixer/src/AvatarAudioRingBuffer.h b/audio-mixer/src/AvatarAudioRingBuffer.h index 7efb503f16..d35113e65e 100644 --- a/audio-mixer/src/AvatarAudioRingBuffer.h +++ b/audio-mixer/src/AvatarAudioRingBuffer.h @@ -9,20 +9,29 @@ #ifndef __hifi__AvatarAudioRingBuffer__ #define __hifi__AvatarAudioRingBuffer__ +#include +#include + #include "PositionalAudioRingBuffer.h" +typedef std::map TwoPoleAgentMap; + class AvatarAudioRingBuffer : public PositionalAudioRingBuffer { public: AvatarAudioRingBuffer(); + ~AvatarAudioRingBuffer(); int parseData(unsigned char* sourceBuffer, int numBytes); + TwoPoleAgentMap& getTwoPoles() { return _twoPoles; } + bool shouldLoopbackForAgent() const { return _shouldLoopbackForAgent; } private: // disallow copying of AvatarAudioRingBuffer objects AvatarAudioRingBuffer(const AvatarAudioRingBuffer&); AvatarAudioRingBuffer& operator= (const AvatarAudioRingBuffer&); + TwoPoleAgentMap _twoPoles; bool _shouldLoopbackForAgent; }; diff --git a/audio-mixer/src/main.cpp b/audio-mixer/src/main.cpp index fbcba0979f..362be4506e 100644 --- a/audio-mixer/src/main.cpp +++ b/audio-mixer/src/main.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include "InjectedAudioRingBuffer.h" #include "AvatarAudioRingBuffer.h" @@ -46,7 +47,7 @@ const unsigned short MIXER_LISTEN_PORT = 55443; const short JITTER_BUFFER_MSECS = 12; const short JITTER_BUFFER_SAMPLES = JITTER_BUFFER_MSECS * (SAMPLE_RATE / 1000.0); -const float BUFFER_SEND_INTERVAL_USECS = (BUFFER_LENGTH_SAMPLES_PER_CHANNEL / SAMPLE_RATE) * 1000000; +const long long BUFFER_SEND_INTERVAL_USECS = floorf((BUFFER_LENGTH_SAMPLES_PER_CHANNEL / SAMPLE_RATE) * 1000000); const long MAX_SAMPLE_VALUE = std::numeric_limits::max(); const long MIN_SAMPLE_VALUE = std::numeric_limits::min(); @@ -57,7 +58,7 @@ void plateauAdditionOfSamples(int16_t &mixSample, int16_t sampleToAdd) { long normalizedSample = std::min(MAX_SAMPLE_VALUE, sumSample); normalizedSample = std::max(MIN_SAMPLE_VALUE, sumSample); - mixSample = normalizedSample; + mixSample = normalizedSample; } void attachNewBufferToAgent(Agent *newAgent) { @@ -70,9 +71,20 @@ void attachNewBufferToAgent(Agent *newAgent) { } } +bool wantLocalDomain = false; + int main(int argc, const char* argv[]) { setvbuf(stdout, NULL, _IOLBF, 0); + // Handle Local Domain testing with the --local command line + const char* local = "--local"; + ::wantLocalDomain = cmdOptionExists(argc, argv,local); + if (::wantLocalDomain) { + printf("Local Domain MODE!\n"); + int ip = getLocalAddress(); + sprintf(DOMAIN_IP,"%d.%d.%d.%d", (ip & 0xFF), ((ip >> 8) & 0xFF),((ip >> 16) & 0xFF), ((ip >> 24) & 0xFF)); + } + AgentList* agentList = AgentList::createInstance(AGENT_TYPE_AUDIO_MIXER, MIXER_LISTEN_PORT); ssize_t receivedBytes = 0; @@ -100,12 +112,42 @@ int main(int argc, const char* argv[]) { timeval lastDomainServerCheckIn = {}; + timeval beginSendTime, endSendTime; + float sumFrameTimePercentages = 0.0f; + int numStatCollections = 0; + + // if we'll be sending stats, call the Logstash::socket() method to make it load the logstash IP outside the loop + if (Logstash::shouldSendStats()) { + Logstash::socket(); + } + while (true) { + if (Logstash::shouldSendStats()) { + gettimeofday(&beginSendTime, NULL); + } // send a check in packet to the domain server if DOMAIN_SERVER_CHECK_IN_USECS has elapsed if (usecTimestampNow() - usecTimestamp(&lastDomainServerCheckIn) >= DOMAIN_SERVER_CHECK_IN_USECS) { gettimeofday(&lastDomainServerCheckIn, NULL); AgentList::getInstance()->sendDomainServerCheckIn(); + + if (Logstash::shouldSendStats() && numStatCollections > 0) { + // if we should be sending stats to Logstash send the appropriate average now + const char MIXER_LOGSTASH_METRIC_NAME[] = "audio-mixer-frame-time-usage"; + + // we're sending a floating point percentage with two mandatory numbers after decimal point + // that could be up to 6 bytes + const int MIXER_LOGSTASH_PACKET_BYTES = strlen(MIXER_LOGSTASH_METRIC_NAME) + 7; + char logstashPacket[MIXER_LOGSTASH_PACKET_BYTES]; + + float averageFrameTimePercentage = sumFrameTimePercentages / numStatCollections; + int packetBytes = sprintf(logstashPacket, "%s %.2f", MIXER_LOGSTASH_METRIC_NAME, averageFrameTimePercentage); + + agentList->getAgentSocket()->send(Logstash::socket(), logstashPacket, packetBytes); + + sumFrameTimePercentages = 0.0f; + numStatCollections = 0; + } } for (AgentList::iterator agent = agentList->begin(); agent != agentList->end(); agent++) { @@ -136,6 +178,8 @@ int main(int argc, const char* argv[]) { int numSamplesDelay = 0; float weakChannelAmplitudeRatio = 1.0f; + stk::TwoPole* otherAgentTwoPole = NULL; + if (otherAgent != agent) { glm::vec3 listenerPosition = agentRingBuffer->getPosition(); @@ -212,6 +256,26 @@ int main(int argc, const char* argv[]) { float sinRatio = fabsf(sinf(glm::radians(bearingRelativeAngleToSource))); numSamplesDelay = PHASE_DELAY_AT_90 * sinRatio; weakChannelAmplitudeRatio = 1 - (PHASE_AMPLITUDE_RATIO_AT_90 * sinRatio); + + // grab the TwoPole object for this source, add it if it doesn't exist + TwoPoleAgentMap& agentTwoPoles = agentRingBuffer->getTwoPoles(); + TwoPoleAgentMap::iterator twoPoleIterator = agentTwoPoles.find(otherAgent->getAgentID()); + + if (twoPoleIterator == agentTwoPoles.end()) { + // setup the freeVerb effect for this source for this client + otherAgentTwoPole = agentTwoPoles[otherAgent->getAgentID()] = new stk::TwoPole; + } else { + otherAgentTwoPole = twoPoleIterator->second; + } + + // calculate the reasonance for this TwoPole based on angle to source + float TWO_POLE_CUT_OFF_FREQUENCY = 800.0f; + float TWO_POLE_MAX_FILTER_STRENGTH = 0.4f; + + otherAgentTwoPole->setResonance(TWO_POLE_CUT_OFF_FREQUENCY, + TWO_POLE_MAX_FILTER_STRENGTH + * fabsf(bearingRelativeAngleToSource) / 180.0f, + true); } } @@ -237,6 +301,10 @@ int main(int argc, const char* argv[]) { plateauAdditionOfSamples(delayedChannel[s], earlierSample); } + if (otherAgentTwoPole) { + otherAgentBuffer->getNextOutput()[s] = otherAgentTwoPole->tick(otherAgentBuffer->getNextOutput()[s]); + } + int16_t currentSample = otherAgentBuffer->getNextOutput()[s] * attenuationCoefficient; plateauAdditionOfSamples(goodChannel[s], currentSample); @@ -318,6 +386,22 @@ int main(int argc, const char* argv[]) { } } + if (Logstash::shouldSendStats()) { + // send a packet to our logstash instance + + // calculate the percentage value for time elapsed for this send (of the max allowable time) + gettimeofday(&endSendTime, NULL); + + float percentageOfMaxElapsed = ((float) (usecTimestamp(&endSendTime) - usecTimestamp(&beginSendTime)) + / BUFFER_SEND_INTERVAL_USECS) * 100.0f; + + if (percentageOfMaxElapsed > 0) { + sumFrameTimePercentages += percentageOfMaxElapsed; + } + + numStatCollections++; + } + long long usecToSleep = usecTimestamp(&startTime) + (++nextFrame * BUFFER_SEND_INTERVAL_USECS) - usecTimestampNow(); if (usecToSleep > 0) { diff --git a/libraries/audio/CMakeLists.txt b/libraries/audio/CMakeLists.txt index 686986340f..6070649060 100644 --- a/libraries/audio/CMakeLists.txt +++ b/libraries/audio/CMakeLists.txt @@ -15,8 +15,4 @@ include(${MACRO_DIR}/IncludeGLM.cmake) include_glm(${TARGET_NAME} ${ROOT_DIR}) include(${MACRO_DIR}/LinkHifiLibrary.cmake) -link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR}) - -# link the threads library -find_package(Threads REQUIRED) -target_link_libraries(${TARGET_NAME} ${CMAKE_THREAD_LIBS_INIT}) \ No newline at end of file +link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR}) \ No newline at end of file diff --git a/libraries/shared/src/Logstash.cpp b/libraries/shared/src/Logstash.cpp new file mode 100644 index 0000000000..dbbf7f8ec3 --- /dev/null +++ b/libraries/shared/src/Logstash.cpp @@ -0,0 +1,45 @@ +// +// Logstash.cpp +// hifi +// +// Created by Stephen Birarda on 6/11/13. +// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// + +#include +#include +#include + +#include "SharedUtil.h" + +#include "Logstash.h" + +sockaddr_in Logstash::logstashSocket = {}; + +sockaddr* Logstash::socket() { + + if (logstashSocket.sin_addr.s_addr == 0) { + // we need to construct the socket object + + // assume IPv4 + logstashSocket.sin_family = AF_INET; + + // use the constant port + logstashSocket.sin_port = htons(LOGSTASH_UDP_PORT); + + // lookup the IP address for the constant hostname + struct hostent* logstashHostInfo; + if ((logstashHostInfo = gethostbyname(LOGSTASH_HOSTNAME))) { + memcpy(&logstashSocket.sin_addr, logstashHostInfo->h_addr_list[0], logstashHostInfo->h_length); + } else { + printf("Failed to lookup logstash IP - will try again on next log attempt.\n"); + } + } + + return (sockaddr*) &logstashSocket; +} + +bool Logstash::shouldSendStats() { + static bool shouldSendStats = isInEnvironment("production"); + return shouldSendStats; +} \ No newline at end of file diff --git a/libraries/shared/src/Logstash.h b/libraries/shared/src/Logstash.h new file mode 100644 index 0000000000..20f58b8057 --- /dev/null +++ b/libraries/shared/src/Logstash.h @@ -0,0 +1,25 @@ +// +// Logstash.h +// hifi +// +// Created by Stephen Birarda on 6/11/13. +// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// + +#ifndef __hifi__Logstash__ +#define __hifi__Logstash__ + +#include + +const int LOGSTASH_UDP_PORT = 9500; +const char LOGSTASH_HOSTNAME[] = "graphite.highfidelity.io"; + +class Logstash { +public: + static sockaddr* socket(); + static bool shouldSendStats(); +private: + static sockaddr_in logstashSocket; +}; + +#endif /* defined(__hifi__Logstash__) */ diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index 0ec2c6e302..9c4748f501 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -110,6 +110,15 @@ void setSemiNibbleAt(unsigned char& byte, int bitIndex, int value) { byte += ((value & 3) << (6 - bitIndex)); // semi-nibbles store 00, 01, 10, or 11 } +bool isInEnvironment(const char* environment) { + char* environmentString = getenv("HIFI_ENVIRONMENT"); + + if (environmentString && strcmp(environmentString, environment) == 0) { + return true; + } else { + return false; + } +} void switchToResourcesParentIfRequired() { #ifdef __APPLE__ diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index e227137470..57dc75c7b2 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -57,6 +57,7 @@ void setAtBit(unsigned char& byte, int bitIndex); int getSemiNibbleAt(unsigned char& byte, int bitIndex); void setSemiNibbleAt(unsigned char& byte, int bitIndex, int value); +bool isInEnvironment(const char* environment); void switchToResourcesParentIfRequired();