diff --git a/src/engine/agent/Agent_AudioManager.cpp b/src/engine/agent/Agent_AudioManager.cpp index 0145bd8f..e2c41774 100644 --- a/src/engine/agent/Agent_AudioManager.cpp +++ b/src/engine/agent/Agent_AudioManager.cpp @@ -26,16 +26,6 @@ AudioManager::~AudioManager() // stop(); } -AudioManager& AudioManager::instance() -{ - static std::shared_ptr GAudioManager; - - if (!GAudioManager) - GAudioManager = std::make_shared(); - - return *GAudioManager; -} - void AudioManager::setTerminal(MT::Terminal* terminal) { mTerminal = terminal; @@ -67,6 +57,7 @@ void AudioManager::start(int usageId) if (mUsage.obtain(usageId) > 1) return; + // Maybe it is time to initialize global audio support if (Audio::OsEngine::instance()) Audio::OsEngine::instance()->open(); @@ -89,14 +80,15 @@ void AudioManager::start(int usageId) enumerator->open(Audio::myMicrophone); int inputIndex = enumerator->indexOfDefaultDevice(); - // Construct and set to terminal's audio pair input device + // Construct default platform input device if (usageId != atNull) mAudioInput = Audio::PInputDevice(Audio::InputDevice::make(enumerator->idAt(inputIndex))); else mAudioInput = Audio::PInputDevice(new Audio::NullInputDevice()); - - mTerminal->audio()->setInput(mAudioInput); } + // Bind input to the terminal's device pair regardless of whether it was + // just constructed or externally injected via setAudioInput(). + mTerminal->audio()->setInput(mAudioInput); if (!mAudioOutput) { @@ -104,7 +96,7 @@ void AudioManager::start(int usageId) enumerator->open(Audio::mySpeaker); int outputIndex = enumerator->indexOfDefaultDevice(); - // Construct and set terminal's audio pair output device + // Construct default platform output device if (usageId != atNull) { if (outputIndex >= enumerator->count()) @@ -115,9 +107,8 @@ void AudioManager::start(int usageId) } else mAudioOutput = Audio::POutputDevice(new Audio::NullOutputDevice()); - - mTerminal->audio()->setOutput(mAudioOutput); } + mTerminal->audio()->setOutput(mAudioOutput); } // Open audio @@ -173,6 +164,12 @@ void AudioManager::setAudioInput(Audio::PInputDevice input) mAudioInput = std::move(input); } +void AudioManager::setAudioOutput(Audio::POutputDevice output) +{ + LOCK_MANAGER; + mAudioOutput = std::move(output); +} + void AudioManager::startPlayFile(int usageId, const std::string& path, AudioTarget target, LoopMode lm, int timelimit) { // Check if file exists @@ -208,6 +205,6 @@ void AudioManager::process() mPlayer.releasePlayed(); std::vector ids; mTerminal->audio()->player().retrieveUsageIds(ids); - for (unsigned i=0; i +#include "audio/Audio_Null.h" +// #include const std::string Status_Ok = "ok"; @@ -37,19 +38,19 @@ AgentImpl::~AgentImpl() // Get access to internal audio manager. Value can be nullptr. const std::shared_ptr& AgentImpl::audioManager() const { - return mAudioManager; + return mAudioManager; } void AgentImpl::setAudioMonitoring(Audio::DataConnection* monitoring) { - mAudioMonitoring = monitoring; - if (mAudioManager) - mAudioManager->setAudioMonitoring(monitoring); + mAudioMonitoring = monitoring; + if (mAudioManager) + mAudioManager->setAudioMonitoring(monitoring); } Audio::DataConnection* AgentImpl::monitoring() const { - return mAudioMonitoring; + return mAudioMonitoring; } void AgentImpl::run() @@ -84,73 +85,73 @@ std::string AgentImpl::command(const std::string& command) if (cmd == "config") processConfig(d, answer); else - if (cmd == "start") - processStart(d, answer); - else - if (cmd == "stop") - processStop(d, answer); - else - if (cmd == "account_create") - processCreateAccount(d, answer); - else - if (cmd == "account_start") - processStartAccount(d, answer); - else - if (cmd == "account_setuserinfo") - processSetUserInfoToAccount(d, answer); - else - if (cmd == "session_create") { - // For Bugsnag test - // int* v = nullptr; - // *v = 0; - processCreateSession(d, answer); - } - else - if (cmd == "session_start") - processStartSession(d, answer); - else - if (cmd == "session_stop") - processStopSession(d, answer); - else - if (cmd == "session_accept") - processAcceptSession(d, answer); - else - if (cmd == "session_destroy") - processDestroySession(d, answer); - else - if (cmd == "session_use_stream") - processUseStreamForSession(d, answer); - else - if (cmd == "wait_for_event") - processWaitForEvent(d, answer); - else - if (cmd == "session_get_media_stats") - processGetMediaStats(d, answer); - else - if (cmd == "agent_network_changed") - processNetworkChanged(d, answer); - else - if (cmd == "agent_add_root_cert") - processAddRootCert(d, answer); - else - if (cmd == "detach_log") - { - GLogger.closeFile(); - answer["status"] = Status_Ok; - } - else - if (cmd == "attach_log") - { - GLogger.openFile(); - answer["status"] = Status_Ok; - } - else - if (cmd == "log_message") - processLogMessage(d, answer); - else - { - answer["status"] = Status_NoCommand; - } + if (cmd == "start") + processStart(d, answer); + else + if (cmd == "stop") + processStop(d, answer); + else + if (cmd == "account_create") + processCreateAccount(d, answer); + else + if (cmd == "account_start") + processStartAccount(d, answer); + else + if (cmd == "account_setuserinfo") + processSetUserInfoToAccount(d, answer); + else + if (cmd == "session_create") { + // For Bugsnag test + // int* v = nullptr; + // *v = 0; + processCreateSession(d, answer); + } + else + if (cmd == "session_start") + processStartSession(d, answer); + else + if (cmd == "session_stop") + processStopSession(d, answer); + else + if (cmd == "session_accept") + processAcceptSession(d, answer); + else + if (cmd == "session_destroy") + processDestroySession(d, answer); + else + if (cmd == "session_use_stream") + processUseStreamForSession(d, answer); + else + if (cmd == "wait_for_event") + processWaitForEvent(d, answer); + else + if (cmd == "session_get_media_stats") + processGetMediaStats(d, answer); + else + if (cmd == "agent_network_changed") + processNetworkChanged(d, answer); + else + if (cmd == "agent_add_root_cert") + processAddRootCert(d, answer); + else + if (cmd == "detach_log") + { + GLogger.closeFile(); + answer["status"] = Status_Ok; + } + else + if (cmd == "attach_log") + { + GLogger.openFile(); + answer["status"] = Status_Ok; + } + else + if (cmd == "log_message") + processLogMessage(d, answer); + else + { + answer["status"] = Status_NoCommand; + } } catch(std::exception& e) { @@ -226,19 +227,13 @@ void AgentImpl::processStart(JsonCpp::Value& request, JsonCpp::Value &answer) for (int i=0; iat(i) = i; - // Disable dynamic payload codec types - commented for now - // if (cl.codecAt(i).payloadType() < 96) - // priorityConfig->at(i) = i; - // else - // priorityConfig->at(i) = -1; - config()[CONFIG_CODEC_PRIORITY] = priorityConfig; // Enable audio mAudioManager = std::make_shared(); mAudioManager->setTerminal(mTerminal.get()); if (mAudioMonitoring) - mAudioManager->setAudioMonitoring(mAudioMonitoring); + mAudioManager->setAudioMonitoring(mAudioMonitoring); // Do not start audio manager here. Start right before call. @@ -341,6 +336,11 @@ void AgentImpl::processStartSession(JsonCpp::Value& request, JsonCpp::Value& ans return; } + if (request["use_null_mic"].asBool()) + mAudioManager->setAudioInput(std::make_shared()); + if (request["use_null_spk"].asBool()) + mAudioManager->setAudioOutput(std::make_shared()); + mAudioManager->start(mUseNativeAudio ? AudioManager::atReceiver : AudioManager::atNull); auto sessionIter = mSessionMap.find(request["session_id"].asInt()); @@ -351,7 +351,7 @@ void AgentImpl::processStartSession(JsonCpp::Value& request, JsonCpp::Value& ans PDataProvider audioProvider = std::make_shared(*this, *mTerminal); audioProvider->setState(audioProvider->state() | static_cast(StreamState::Grabbing) | static_cast(StreamState::Playing)); -/*#if defined(USE_AQUA_LIBRARY) + /*#if defined(USE_AQUA_LIBRARY) std::string path_faults = request["path_faults"].asString(); sevana::aqua::config config = { @@ -440,6 +440,10 @@ void AgentImpl::processAcceptSession(JsonCpp::Value& request, JsonCpp::Value& an else { // Ensure audio manager is here + if (request["use_null_mic"].asBool()) + mAudioManager->setAudioInput(std::make_shared()); + if (request["use_null_spk"].asBool()) + mAudioManager->setAudioOutput(std::make_shared()); mAudioManager->start(mUseNativeAudio ? AudioManager::atReceiver : AudioManager::atNull); // Accept session on SIP level @@ -471,9 +475,9 @@ void AgentImpl::processDestroySession(JsonCpp::Value& request, JsonCpp::Value& a auto sessionIter = mSessionMap.find(sessionId); if (sessionIter != mSessionMap.end()) mSessionMap.erase(sessionIter); -//#if defined(USE_AQUA_LIBRARY) -// closeAqua(sessionId); -//#endif + //#if defined(USE_AQUA_LIBRARY) + // closeAqua(sessionId); + //#endif answer["status"] = Status_Ok; } @@ -637,7 +641,7 @@ void AgentImpl::processUseStreamForSession(JsonCpp::Value& request, JsonCpp::Val // Parse command std::string actionText = request["media_action"].asString(), - directionText = request["media_direction"].asString(); + directionText = request["media_direction"].asString(); MT::Stream::MediaDirection direction = directionText == "incoming" ? MT::Stream::MediaDirection::Incoming : MT::Stream::MediaDirection::Outgoing; @@ -668,34 +672,34 @@ void AgentImpl::processUseStreamForSession(JsonCpp::Value& request, JsonCpp::Val } } else - if (actionText == "write") - { - if (path.empty()) + if (actionText == "write") { - // Turn off recording from the stream - prov->writeFile(Audio::PWavFileWriter(), direction); - answer["status"] = Status_Ok; - } - else - { - Audio::PWavFileWriter writer = std::make_shared(); - if (!writer->open(strx::makeTstring(path), AUDIO_SAMPLERATE, AUDIO_CHANNELS)) - answer["status"] = Status_FailedToOpenFile; - else + if (path.empty()) { - prov->writeFile(writer, direction); + // Turn off recording from the stream + prov->writeFile(Audio::PWavFileWriter(), direction); answer["status"] = Status_Ok; } + else + { + Audio::PWavFileWriter writer = std::make_shared(); + if (!writer->open(strx::makeTstring(path), AUDIO_SAMPLERATE, AUDIO_CHANNELS)) + answer["status"] = Status_FailedToOpenFile; + else + { + prov->writeFile(writer, direction); + answer["status"] = Status_Ok; + } + } } - } - else - if (actionText == "mirror") - { - prov->setupMirror(request["enable"].asBool()); - answer["status"] = Status_Ok; - } - else - answer["status"] = Status_NoCommand; + else + if (actionText == "mirror") + { + prov->setupMirror(request["enable"].asBool()); + answer["status"] = Status_Ok; + } + else + answer["status"] = Status_NoCommand; } else answer["status"] = Status_NoMediaAction; diff --git a/src/engine/agent/Agent_Impl.h b/src/engine/agent/Agent_Impl.h index b0209253..76c6496d 100644 --- a/src/engine/agent/Agent_Impl.h +++ b/src/engine/agent/Agent_Impl.h @@ -18,111 +18,111 @@ class AgentImpl: public UserAgent, public MT::Stream::MediaObserver { protected: - std::recursive_mutex mAgentMutex; - std::mutex mEventListMutex; - std::condition_variable mEventListChangeCondVar; - std::vector mEventList; - bool mUseNativeAudio = false; + std::recursive_mutex mAgentMutex; + std::mutex mEventListMutex; + std::condition_variable mEventListChangeCondVar; + std::vector mEventList; + bool mUseNativeAudio = false; - typedef std::map AccountMap; - AccountMap mAccountMap; + typedef std::map AccountMap; + AccountMap mAccountMap; - typedef std::map SessionMap; - SessionMap mSessionMap; + typedef std::map SessionMap; + SessionMap mSessionMap; - std::shared_ptr mThread; - volatile bool mShutdown; - std::shared_ptr mTerminal; - std::shared_ptr mAudioManager; - Audio::DataConnection* mAudioMonitoring = nullptr; + std::shared_ptr mThread; + volatile bool mShutdown; + std::shared_ptr mTerminal; + std::shared_ptr mAudioManager; + Audio::DataConnection* mAudioMonitoring = nullptr; - void run(); - void addEvent(const JsonCpp::Value& v); - void processConfig(JsonCpp::Value& request, JsonCpp::Value& answer); - void processStart(JsonCpp::Value& request, JsonCpp::Value& answer); - void processStop(JsonCpp::Value& request, JsonCpp::Value& answer); - void processCreateAccount(JsonCpp::Value& request, JsonCpp::Value& answer); - void processStartAccount(JsonCpp::Value& request, JsonCpp::Value& answer); - void processSetUserInfoToAccount(JsonCpp::Value& request, JsonCpp::Value& answer); - void processCreateSession(JsonCpp::Value& request, JsonCpp::Value& answer); - void processStartSession(JsonCpp::Value& request, JsonCpp::Value& answer); - void processStopSession(JsonCpp::Value& request, JsonCpp::Value& answer); - void processAcceptSession(JsonCpp::Value& request, JsonCpp::Value& answer); - void processDestroySession(JsonCpp::Value& request, JsonCpp::Value& answer); - void processWaitForEvent(JsonCpp::Value& request, JsonCpp::Value& answer); - void processGetMediaStats(JsonCpp::Value& request, JsonCpp::Value& answer); - void processUseStreamForSession(JsonCpp::Value& request, JsonCpp::Value& answer); - void processNetworkChanged(JsonCpp::Value& request, JsonCpp::Value& answer); - void processAddRootCert(JsonCpp::Value& request, JsonCpp::Value& answer); - void processLogMessage(JsonCpp::Value& request, JsonCpp::Value& answer); - void stopAgentAndThread(); + void run(); + void addEvent(const JsonCpp::Value& v); + void processConfig(JsonCpp::Value& request, JsonCpp::Value& answer); + void processStart(JsonCpp::Value& request, JsonCpp::Value& answer); + void processStop(JsonCpp::Value& request, JsonCpp::Value& answer); + void processCreateAccount(JsonCpp::Value& request, JsonCpp::Value& answer); + void processStartAccount(JsonCpp::Value& request, JsonCpp::Value& answer); + void processSetUserInfoToAccount(JsonCpp::Value& request, JsonCpp::Value& answer); + void processCreateSession(JsonCpp::Value& request, JsonCpp::Value& answer); + void processStartSession(JsonCpp::Value& request, JsonCpp::Value& answer); + void processStopSession(JsonCpp::Value& request, JsonCpp::Value& answer); + void processAcceptSession(JsonCpp::Value& request, JsonCpp::Value& answer); + void processDestroySession(JsonCpp::Value& request, JsonCpp::Value& answer); + void processWaitForEvent(JsonCpp::Value& request, JsonCpp::Value& answer); + void processGetMediaStats(JsonCpp::Value& request, JsonCpp::Value& answer); + void processUseStreamForSession(JsonCpp::Value& request, JsonCpp::Value& answer); + void processNetworkChanged(JsonCpp::Value& request, JsonCpp::Value& answer); + void processAddRootCert(JsonCpp::Value& request, JsonCpp::Value& answer); + void processLogMessage(JsonCpp::Value& request, JsonCpp::Value& answer); + void stopAgentAndThread(); public: - AgentImpl(); - ~AgentImpl(); + AgentImpl(); + ~AgentImpl(); - std::string command(const std::string& command); - bool waitForData(int milliseconds); - std::string read(); + std::string command(const std::string& command); + bool waitForData(int milliseconds); + std::string read(); - // Get access to internal audio manager. Value can be nullptr. - const std::shared_ptr& audioManager() const; + // Get access to internal audio manager. Value can be nullptr. + const std::shared_ptr& audioManager() const; - void setAudioMonitoring(Audio::DataConnection* monitoring); - Audio::DataConnection* monitoring() const; + void setAudioMonitoring(Audio::DataConnection* monitoring); + Audio::DataConnection* monitoring() const; - // UserAgent overrides - // Called on new incoming session; providers shoukld - PDataProvider onProviderNeeded(const std::string& name) override; + // UserAgent overrides + // Called on new incoming session; providers shoukld + PDataProvider onProviderNeeded(const std::string& name) override; - // Called on new session offer - void onNewSession(PSession s) override; + // Called on new session offer + void onNewSession(PSession s) override; - // Called when session is terminated - void onSessionTerminated(PSession s, int responsecode, int reason) override; + // Called when session is terminated + void onSessionTerminated(PSession s, int responsecode, int reason) override; - // Called when session is established ok i.e. after all ICE signalling is finished - // Conntype is type of establish event - EV_SIP or EV_ICE - void onSessionEstablished(PSession s, int conntype, const RtpPair& p) override; + // Called when session is established ok i.e. after all ICE signalling is finished + // Conntype is type of establish event - EV_SIP or EV_ICE + void onSessionEstablished(PSession s, int conntype, const RtpPair& p) override; - void onSessionProvisional(PSession s, int code) override; + void onSessionProvisional(PSession s, int code) override; - // Called when user agent started - void onStart(int errorcode) override; + // Called when user agent started + void onStart(int errorcode) override; - // Called when user agent stopped - void onStop() override; + // Called when user agent stopped + void onStop() override; - // Called when account registered - void onAccountStart(PAccount account) override; + // Called when account registered + void onAccountStart(PAccount account) override; - // Called when account removed or failed (non zero error code) - void onAccountStop(PAccount account, int error) override; + // Called when account removed or failed (non zero error code) + void onAccountStop(PAccount account, int error) override; - // Called when connectivity checks failed. - void onConnectivityFailed(PSession s) override; + // Called when connectivity checks failed. + void onConnectivityFailed(PSession s) override; - // Called when new candidate is gathered - void onCandidateGathered(PSession s, const char* address) override; + // Called when new candidate is gathered + void onCandidateGathered(PSession s, const char* address) override; - // Called when network change detected - void onNetworkChange(PSession s) override; + // Called when network change detected + void onNetworkChange(PSession s) override; - // Called when all candidates are gathered - void onGathered(PSession s) override; + // Called when all candidates are gathered + void onGathered(PSession s) override; - // Called when new connectivity check is finished - void onCheckFinished(PSession s, const char* description) override; + // Called when new connectivity check is finished + void onCheckFinished(PSession s, const char* description) override; - // Called when log message must be recorded - void onLog(const char* msg) override; + // Called when log message must be recorded + void onLog(const char* msg) override; - // Called when problem with SIP connection(s) detected - void onSipConnectionFailed() override; + // Called when problem with SIP connection(s) detected + void onSipConnectionFailed() override; - // Called on incoming & outgoing audio for voice sessions - void onMedia(const void* data, int length, MT::Stream::MediaDirection direction, void* context, void* userTag) override; + // Called on incoming & outgoing audio for voice sessions + void onMedia(const void* data, int length, MT::Stream::MediaDirection direction, void* context, void* userTag) override; }; #endif diff --git a/src/engine/audio/Audio_Null.cpp b/src/engine/audio/Audio_Null.cpp index 5121e9a9..c29bf6d8 100644 --- a/src/engine/audio/Audio_Null.cpp +++ b/src/engine/audio/Audio_Null.cpp @@ -5,129 +5,135 @@ #define LOG_SUBSYSTEM "audio" using namespace Audio; +using namespace std::chrono_literals; -NullTimer::NullTimer(int interval, Delegate *delegate, const char* name) - :mShutdown(false), mDelegate(delegate), mInterval(interval), mThreadName(name) +NullTimer::NullTimer(std::chrono::milliseconds interval, Delegate *delegate, const char* name) + :mShutdown(false), mDelegate(delegate), mInterval(interval), mThreadName(name) { - start(); + start(); } NullTimer::~NullTimer() { - stop(); + stop(); } void NullTimer::start() { - mShutdown = false; - mWorkerThread = std::thread(&NullTimer::run, this); + mShutdown = false; + mWorkerThread = std::thread(&NullTimer::run, this); } void NullTimer::stop() { - mShutdown = true; - if (mWorkerThread.joinable()) - mWorkerThread.join(); + mShutdown = true; + if (mWorkerThread.joinable()) + mWorkerThread.join(); } void NullTimer::run() { - mTail = 0; - while (!mShutdown) - { - // Get current timestamp - std::chrono::system_clock::time_point timestamp = std::chrono::system_clock::now(); - - while (mTail >= mInterval * 1000) + mTail = 0us; + while (!mShutdown) { - if (mDelegate) - mDelegate->onTimerSignal(*this); - mTail -= mInterval * 1000; + // Get current timestamp + std::chrono::system_clock::time_point timestamp = std::chrono::system_clock::now(); + + while (mTail >= mInterval) + { + if (mDelegate) + mDelegate->onTimerSignal(*this); + mTail -= mInterval; + } + + // Sleep for mInterval - mTail milliseconds + std::this_thread::sleep_for(mInterval - mTail); + + mTail = mTail + std::chrono::duration_cast(std::chrono::system_clock::now() - timestamp); } - - // Sleep for mInterval - mTail milliseconds - std::this_thread::sleep_for(std::chrono::microseconds(mInterval * 1000 - mTail)); - - mTail += (int)std::chrono::duration_cast(std::chrono::system_clock::now() - timestamp).count(); - } } // --------------------- NullInputDevice ------------------------- NullInputDevice::NullInputDevice() - :mBuffer(nullptr) + :mBuffer(nullptr) { } NullInputDevice::~NullInputDevice() { - internalClose(); + internalClose(); } bool NullInputDevice::open() { - mBuffer = malloc(AUDIO_MIC_BUFFER_SIZE); - memset(mBuffer, 0, AUDIO_MIC_BUFFER_SIZE); - mTimeCounter = 0; mDataCounter = 0; - // Creation of timer starts it also. So first onTimerSignal can come even before open() returns. - mTimer = std::make_shared(AUDIO_MIC_BUFFER_LENGTH, this, "NullMicrophoneThread"); - return true; + ICELogInfo(<< "Starting NullInputDevice for " << AUDIO_MIC_BUFFER_LENGTH << "ms buffers"); + mBuffer = malloc(AUDIO_MIC_BUFFER_SIZE); + memset(mBuffer, 0, AUDIO_MIC_BUFFER_SIZE); + mTimeCounter = 0; mDataCounter = 0; + + // Creation of timer starts it also. So first onTimerSignal can come even before open() returns. + mTimer = std::make_shared(std::chrono::milliseconds(AUDIO_MIC_BUFFER_LENGTH), this, "null_mic"); + return true; } void NullInputDevice::internalClose() { - mTimer.reset(); - if (mBuffer) - { - free(mBuffer); - mBuffer = nullptr; - } - ICELogInfo(<<"Pseudocaptured " << mTimeCounter << " milliseconds , " << mDataCounter << " bytes."); + ICELogInfo(<< "Stopping NullInputDevice"); + mTimer.reset(); + if (mBuffer) + { + free(mBuffer); + mBuffer = nullptr; + } + ICELogInfo( << "Pseudocaptured " << mTimeCounter << " milliseconds , " << mDataCounter << " bytes."); } void NullInputDevice::close() { internalClose(); } + Format NullInputDevice::getFormat() { - assert (Format().sizeFromTime(AUDIO_MIC_BUFFER_LENGTH) == AUDIO_MIC_BUFFER_SIZE); - return Format(); + assert (Format().sizeFromTime(AUDIO_MIC_BUFFER_LENGTH) == AUDIO_MIC_BUFFER_SIZE); + + return {}; // Return library-define default format } void NullInputDevice::onTimerSignal(NullTimer& timer) { - mTimeCounter += AUDIO_MIC_BUFFER_LENGTH; - mDataCounter += AUDIO_MIC_BUFFER_SIZE; - if (mConnection) - mConnection->onMicData(getFormat(), mBuffer, AUDIO_MIC_BUFFER_SIZE); + mTimeCounter += AUDIO_MIC_BUFFER_LENGTH; + mDataCounter += AUDIO_MIC_BUFFER_SIZE; + if (mConnection) + mConnection->onMicData(getFormat(), mBuffer, AUDIO_MIC_BUFFER_SIZE); } // --------------------- NullOutputDevice -------------------------- NullOutputDevice::NullOutputDevice() - :mBuffer(nullptr) + :mBuffer(nullptr) { } NullOutputDevice::~NullOutputDevice() { - internalClose(); + internalClose(); } bool NullOutputDevice::open() { - mTimeCounter = 0; mDataCounter = 0; - mBuffer = malloc(AUDIO_SPK_BUFFER_SIZE); - // Creation of timer starts it also. So first onSpkData() can come before open() returns even. - mTimer = std::make_shared(AUDIO_SPK_BUFFER_LENGTH, this, "NullSpeakerThread"); - return true; + mTimeCounter = 0; mDataCounter = 0; + mBuffer = malloc(AUDIO_SPK_BUFFER_SIZE); + // Creation of timer starts it also. So first onSpkData() can come before open() returns even. + mTimer = std::make_shared(std::chrono::milliseconds(AUDIO_SPK_BUFFER_LENGTH), this, "null_spk"); + return true; } void NullOutputDevice::internalClose() { - mTimer.reset(); - free(mBuffer); mBuffer = nullptr; - ICELogInfo(<< "Pseudoplayed " << mTimeCounter << " milliseconds, " << mDataCounter << " bytes."); + mTimer.reset(); + free(mBuffer); mBuffer = nullptr; + ICELogInfo(<< "Pseudoplayed " << mTimeCounter << " milliseconds, " << mDataCounter << " bytes."); } void NullOutputDevice::close() @@ -137,16 +143,16 @@ void NullOutputDevice::close() Format NullOutputDevice::getFormat() { - assert (Format().sizeFromTime(AUDIO_SPK_BUFFER_LENGTH) == AUDIO_SPK_BUFFER_SIZE); - return Format(); + assert (Format().sizeFromTime(AUDIO_SPK_BUFFER_LENGTH) == AUDIO_SPK_BUFFER_SIZE); + return Format(); } void NullOutputDevice::onTimerSignal(NullTimer &timer) { - mTimeCounter += AUDIO_SPK_BUFFER_LENGTH; - mDataCounter += AUDIO_SPK_BUFFER_SIZE; - if (mConnection) - mConnection->onSpkData(getFormat(), mBuffer, AUDIO_SPK_BUFFER_SIZE); + mTimeCounter += AUDIO_SPK_BUFFER_LENGTH; + mDataCounter += AUDIO_SPK_BUFFER_SIZE; + if (mConnection) + mConnection->onSpkData(getFormat(), mBuffer, AUDIO_SPK_BUFFER_SIZE); } // ---------------------- NullEnumerator -------------------------- @@ -164,25 +170,25 @@ void NullEnumerator::close() int NullEnumerator::count() { - return 1; + return 1; } std::tstring NullEnumerator::nameAt(int index) { #if defined(TARGET_WIN) - return L"null"; + return L"null"; #else - return "null"; + return "null"; #endif } int NullEnumerator::idAt(int index) { - return 0; + return 0; } int NullEnumerator::indexOfDefaultDevice() { - return 0; + return 0; } diff --git a/src/engine/audio/Audio_Null.h b/src/engine/audio/Audio_Null.h index aa1c5749..b3bbd42e 100644 --- a/src/engine/audio/Audio_Null.h +++ b/src/engine/audio/Audio_Null.h @@ -2,45 +2,46 @@ #define __AUDIO_NULL_H #include +#include #include "Audio_Interface.h" namespace Audio { - class NullTimer - { - public: +class NullTimer +{ +public: class Delegate { public: - virtual void onTimerSignal(NullTimer& timer) = 0; + virtual void onTimerSignal(NullTimer& timer) = 0; }; - protected: +protected: std::thread mWorkerThread; - volatile bool mShutdown; - Delegate* mDelegate; - int mInterval, // Interval - wanted number of milliseconds - mTail; // Number of milliseconds that can be sent immediately to sink + std::atomic_bool mShutdown = {false}; + Delegate* mDelegate = nullptr; + std::chrono::milliseconds mInterval; // Interval - wanted number of milliseconds + std::chrono::microseconds mTail; // Number of milliseconds that can be sent immediately to sink std::string mThreadName; void start(); void stop(); void run(); - public: +public: /* Interval is in milliseconds. */ - NullTimer(int interval, Delegate* delegate, const char* name = nullptr); + NullTimer(std::chrono::milliseconds interval, Delegate* delegate, const char* name = nullptr); ~NullTimer(); - }; +}; - class NullInputDevice: public InputDevice, public NullTimer::Delegate - { - protected: +class NullInputDevice: public InputDevice, public NullTimer::Delegate +{ +protected: void* mBuffer = nullptr; std::shared_ptr mTimer; int64_t mTimeCounter = 0, mDataCounter = 0; void internalClose(); - public: +public: NullInputDevice(); virtual ~NullInputDevice(); @@ -49,17 +50,17 @@ namespace Audio Format getFormat() override; void onTimerSignal(NullTimer& timer) override; - }; +}; - class NullOutputDevice: public OutputDevice, public NullTimer::Delegate - { - protected: +class NullOutputDevice: public OutputDevice, public NullTimer::Delegate +{ +protected: std::shared_ptr mTimer; void* mBuffer = nullptr; int64_t mDataCounter = 0, mTimeCounter = 0; void internalClose(); - public: +public: NullOutputDevice(); virtual ~NullOutputDevice(); @@ -68,11 +69,11 @@ namespace Audio Format getFormat() override; void onTimerSignal(NullTimer& timer) override; - }; +}; - class NullEnumerator: public Enumerator - { - public: +class NullEnumerator: public Enumerator +{ +public: NullEnumerator(); ~NullEnumerator(); @@ -83,8 +84,8 @@ namespace Audio std::tstring nameAt(int index) override; int idAt(int index) override; int indexOfDefaultDevice() override; +}; - }; } #endif diff --git a/src/engine/endpoint/EP_Engine.h b/src/engine/endpoint/EP_Engine.h index e264a21e..92103971 100644 --- a/src/engine/endpoint/EP_Engine.h +++ b/src/engine/endpoint/EP_Engine.h @@ -108,7 +108,7 @@ enum CONFIG_ACCOUNT, // VariantMap with account configuration CONFIG_EXTERNALIP, // Use external/public IP in outgoing requests CONFIG_OWN_DNS, // Use predefined DNS servers - CONFIG_REGID // reg-id value from RFC5626 + CONFIG_REGID // reg-id value from RFC5626, }; // Conntype parameter for OnSessionEstablished event diff --git a/src/engine/endpoint/EP_Session.cpp b/src/engine/endpoint/EP_Session.cpp index 74c6c885..4fa93b04 100644 --- a/src/engine/endpoint/EP_Session.cpp +++ b/src/engine/endpoint/EP_Session.cpp @@ -453,7 +453,7 @@ void Session::getSessionInfo(Session::InfoOptions options, VariantMap& info) media = &stream; MT::Statistics s = stream.provider()->getStatistics(); - info[SessionInfo_NetworkMos] = static_cast(s.calculateMos(4.14)); + info[SessionInfo_NetworkMos] = static_cast(s.calculateMos()); info[SessionInfo_AudioCodec] = s.mCodecName; stat += s; diff --git a/src/engine/media/MT_AudioStream.cpp b/src/engine/media/MT_AudioStream.cpp index ffbb7627..c6a1b610 100644 --- a/src/engine/media/MT_AudioStream.cpp +++ b/src/engine/media/MT_AudioStream.cpp @@ -344,14 +344,20 @@ void AudioStream::dataArrived(PDatagramSocket s, const void* buffer, int length, } mStat.mReceived += length; + auto& perDst = mStat.mPerDestination[source]; + perDst.mReceivedBytes += length; if (RtpHelper::isRtp(mReceiveBuffer, receiveLength)) { if (!mStat.mFirstRtpTime) mStat.mFirstRtpTime = std::chrono::steady_clock::now(); mStat.mReceivedRtp++; + perDst.mReceivedRtp++; } else + { mStat.mReceivedRtcp++; + perDst.mReceivedRtcp++; + } mRtpSession.Poll(); // maybe it is extra with external transmitter bool hasData = mRtpSession.GotoFirstSourceWithData(); diff --git a/src/engine/media/MT_NativeRtpSender.cpp b/src/engine/media/MT_NativeRtpSender.cpp index a48b5438..54baa14d 100644 --- a/src/engine/media/MT_NativeRtpSender.cpp +++ b/src/engine/media/MT_NativeRtpSender.cpp @@ -47,6 +47,9 @@ bool NativeRtpSender::SendRTP(const void *data, size_t len) mSocket.mRtp->sendDatagram(mTarget.mRtp, mSendBuffer, sendLength); mStat.mSentRtp++; mStat.mSent += len; + auto& perDst = mStat.mPerDestination[mTarget.mRtp]; + perDst.mSentRtp++; + perDst.mSentBytes += len; return true; } @@ -73,6 +76,9 @@ bool NativeRtpSender::SendRTCP(const void *data, size_t len) mSocket.mRtcp->sendDatagram(mTarget.mRtcp, mSendBuffer, sendLength); mStat.mSentRtcp++; mStat.mSent += len; + auto& perDst = mStat.mPerDestination[mTarget.mRtcp]; + perDst.mSentRtcp++; + perDst.mSentBytes += len; return true; } diff --git a/src/engine/media/MT_Statistics.cpp b/src/engine/media/MT_Statistics.cpp index 67042366..1284e71c 100644 --- a/src/engine/media/MT_Statistics.cpp +++ b/src/engine/media/MT_Statistics.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include "MT_Statistics.h" #define LOG_SUBSYSTEM "media" @@ -203,7 +204,7 @@ void Statistics::calculateBurstr(double* burstr, double* lossr) const *lossr = 0; } -double Statistics::calculateMos(double maximalMos) const +double Statistics::calculateMos() const { // Network MOS via the simplified ITU-T G.107 E-Model: // @@ -211,7 +212,7 @@ double Statistics::calculateMos(double maximalMos) const // Id = 0.024*d + 0.11*max(0, d - 177.3) // Ie_eff = Ie + (95 - Ie) * Ppl / (Ppl + Bpl) (BurstR=1) // R = 93.2 - Id - Ie_eff (clamped to [0,100]) - // MOS = 1 + 0.035*R + 7e-6*R*(R-60)*(100-R) (clamped to [1, maximalMos]) + // MOS = 1 + 0.035*R + 7e-6*R*(R-60)*(100-R) (clamped >= 1) // // Ie/Bpl are looked up from a per-codec table; safe defaults are used // when the codec is unknown. @@ -253,8 +254,7 @@ double Statistics::calculateMos(double maximalMos) const else mos = 1.0 + 0.035 * R + 7e-6 * R * (R - 60.0) * (100.0 - R); - if (mos < 1.0) mos = 1.0; - if (mos > maximalMos) mos = maximalMos; + if (mos < 1.0) mos = 1.0; return mos; } @@ -306,6 +306,17 @@ Statistics& Statistics::operator += (const Statistics& src) mRemotePeer = src.mRemotePeer; mSsrc = src.mSsrc; + for (const auto& [addr, counts]: src.mPerDestination) + { + auto& dst = mPerDestination[addr]; + dst.mSentRtp += counts.mSentRtp; + dst.mSentRtcp += counts.mSentRtcp; + dst.mSentBytes += counts.mSentBytes; + dst.mReceivedRtp += counts.mReceivedRtp; + dst.mReceivedRtcp += counts.mReceivedRtcp; + dst.mReceivedBytes += counts.mReceivedBytes; + } + return *this; } @@ -330,6 +341,19 @@ Statistics& Statistics::operator -= (const Statistics& src) mCodecCount[codecStat.first] -= codecStat.second; } + for (const auto& [addr, counts]: src.mPerDestination) + { + auto it = mPerDestination.find(addr); + if (it == mPerDestination.end()) + continue; + it->second.mSentRtp -= counts.mSentRtp; + it->second.mSentRtcp -= counts.mSentRtcp; + it->second.mSentBytes -= counts.mSentBytes; + it->second.mReceivedRtp -= counts.mReceivedRtp; + it->second.mReceivedRtcp -= counts.mReceivedRtcp; + it->second.mReceivedBytes -= counts.mReceivedBytes; + } + return *this; } @@ -345,5 +369,16 @@ std::string Statistics::toString() const << ", decode requested: " << mDecodeRequested.average() << ", packet interval: " << mPacketInterval.average(); + for (const auto& [addr, counts]: mPerDestination) + { + oss << "; peer " << addr.toBriefStdString() + << " sent rtp=" << counts.mSentRtp + << "/rtcp=" << counts.mSentRtcp + << "/bytes=" << counts.mSentBytes + << ", received rtp=" << counts.mReceivedRtp + << "/rtcp=" << counts.mReceivedRtcp + << "/bytes=" << counts.mReceivedBytes; + } + return oss.str(); } diff --git a/src/engine/media/MT_Statistics.h b/src/engine/media/MT_Statistics.h index 9ab6a52b..d9a36f1b 100644 --- a/src/engine/media/MT_Statistics.h +++ b/src/engine/media/MT_Statistics.h @@ -7,7 +7,7 @@ #include "helper/HL_Statistics.h" #include "helper/HL_Types.h" -#include "helper/HL_InternetAddress.h" +#include "ice/ICEAddress.h" #include "jrtplib/src/rtptimeutilities.h" #include "jrtplib/src/rtppacket.h" @@ -73,6 +73,19 @@ struct Dtmf2833Event std::chrono::microseconds mTimestamp; }; +// Per-remote-address packet/byte counters. Split out so an aggregate +// Statistics can break its totals down by destination/source — useful +// for diagnosing ICE candidate switches or symmetric-RTP issues. +struct DestinationStats +{ + size_t mSentRtp = 0; + size_t mSentRtcp = 0; + size_t mSentBytes = 0; + size_t mReceivedRtp = 0; + size_t mReceivedRtcp = 0; + size_t mReceivedBytes = 0; +}; + class Statistics { public: @@ -88,6 +101,10 @@ public: mPacketDropped = 0, // Number of dropped packets (due to time unsync when playing)б mIllegalRtp = 0; // Number of rtp packets with bad payload type + // Per-remote-address breakdown of the totals above. Keyed by the remote + // RTP/RTCP socket address (NAT-mapped, after ICE selection). + std::map mPerDestination; + TestResult mDecodingInterval, // Average interval on call to packet decode mDecodeRequested, // Average amount of requested audio frames to play mPacketInterval; // Average interval between packet adding to jitter buffer @@ -115,7 +132,7 @@ public: // It is to calculate network MOS void calculateBurstr(double* burstr, double* loss) const; - double calculateMos(double maximalMos) const; + double calculateMos() const; Statistics(); ~Statistics();