diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 229bcf51..7cf184ea 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -88,11 +88,17 @@ if (CMAKE_SYSTEM MATCHES "Darwin*") add_definitions (-DTARGET_OSX) endif() +if (CMAKE_SYSTEM MATCHES "Android") + message("Adding the Oboe library") + set (OBOE_DIR libs/oboe) + add_subdirectory (${OBOE_DIR} ./oboe) + include_directories (${OBOE_DIR}/include) +endif() + if (USE_MUSL) add_definitions(-DTARGET_MUSL) endif() - if (USE_AQUA_LIB) message("Use AQuA library") add_definitions( -DUSE_AQUA_LIBRARY ) @@ -102,7 +108,8 @@ endif() if (USE_PVQA_LIBRARY) message("Use PVQA libraries") add_definitions( -DUSE_PVQA_LIBRARY ) - include_directories(${CMAKE_CURRENT_SOURCE_DIR}/libs/pvqa/include) + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/libs/pvqa/include + ${CMAKE_CURRENT_SOURCE_DIR}/libs/pvqa++/include) endif() set (RTPHONE_SOURCES @@ -201,6 +208,10 @@ else () set (LIBS ${LIBS} dl uuid) endif () +if (CMAKE_SYSTEM MATCHES "Android") + set (LIBS ${LIBS} oboe) +endif() + if (USE_AMR_CODEC) set (LIBS ${LIBS}) endif (USE_AMR_CODEC) @@ -215,7 +226,7 @@ target_link_libraries(rtphone uuid ${OPENSSL_SSL} ${OPENSSL_CRYPTO} - ${LIBS}) + ${LIBS} ) target_include_directories(rtphone diff --git a/src/engine/agent/Agent_Impl.cpp b/src/engine/agent/Agent_Impl.cpp index e52e9819..cdca39f0 100644 --- a/src/engine/agent/Agent_Impl.cpp +++ b/src/engine/agent/Agent_Impl.cpp @@ -171,10 +171,13 @@ void AgentImpl::processConfig(JsonCpp::Value &d, JsonCpp::Value &answer) #endif std::string transport = d["transport"].asString(); - config()[CONFIG_TRANSPORT] = (transport == "any") ? 0 : (transport == "udp" ? 1 : (transport == "tcp" ? 2 : 3)); + config()[CONFIG_TRANSPORT] = (transport == "any") ? TransportType_Any : (transport == "udp" ? TransportType_Udp : (transport == "tcp" ? TransportType_Tcp : TransportType_Tls)); config()[CONFIG_IPV4] = d["ipv4"].asBool(); config()[CONFIG_IPV6] = d["ipv6"].asBool(); + if (transport == "tls") + config()[CONFIG_SIPS] = true; + // Log file std::string logfile = d["logfile"].asString(); ice::Logger& logger = ice::GLogger; @@ -186,10 +189,12 @@ void AgentImpl::processConfig(JsonCpp::Value &d, JsonCpp::Value &answer) mUseNativeAudio = d["nativeaudio"].asBool(); config()[CONFIG_OWN_DNS] = d["dns_servers"].asString(); + config()[CONFIG_SIPS] = d["secure"].asBool(); + answer["status"] = Status_Ok; } -void AgentImpl::processStart(JsonCpp::Value& /*request*/, JsonCpp::Value &answer) +void AgentImpl::processStart(JsonCpp::Value& request, JsonCpp::Value &answer) { std::unique_lock l(mAgentMutex); if (mThread) @@ -198,6 +203,9 @@ void AgentImpl::processStart(JsonCpp::Value& /*request*/, JsonCpp::Value &answer return; // Started already } + // Process config (can be sent via start command as well) + // processConfig(request, answer); + // Start socket thread SocketHeap::instance().start(); diff --git a/src/engine/audio/Audio_Android.h b/src/engine/audio/Audio_Android.h index 35ab5cc4..f1543efb 100644 --- a/src/engine/audio/Audio_Android.h +++ b/src/engine/audio/Audio_Android.h @@ -25,7 +25,6 @@ namespace Audio { - class AndroidEnumerator: public Enumerator { public: diff --git a/src/engine/audio/Audio_AndroidOboe.cpp b/src/engine/audio/Audio_AndroidOboe.cpp new file mode 100644 index 00000000..88334406 --- /dev/null +++ b/src/engine/audio/Audio_AndroidOboe.cpp @@ -0,0 +1,245 @@ +#include "Audio_AndroidOboe.h" +#include "../helper/HL_Sync.h" +#include "../helper/HL_Log.h" +#include +#include +#include + +#include "../helper/HL_String.h" +#include "../helper/HL_Time.h" + +#ifdef TARGET_ANDROID + +#define LOG_SUBSYSTEM "Audio" + +using namespace Audio; + + +// -------------------- AndroidEnumerator ----------------------------- + +AndroidEnumerator::AndroidEnumerator() +{} + +AndroidEnumerator::~AndroidEnumerator() +{} + +int AndroidEnumerator::indexOfDefaultDevice() +{ + return 0; +} + +int AndroidEnumerator::count() +{ + return 1; +} + +int AndroidEnumerator::idAt(int index) +{ + return 0; +} + +std::string AndroidEnumerator::nameAt(int index) +{ + return "Audio"; +} + +void AndroidEnumerator::open(int direction) +{} + +void AndroidEnumerator::close() +{} + +// --------------- Input implementation ---------------- +AndroidInputDevice::AndroidInputDevice(int devId) +{} + +AndroidInputDevice::~AndroidInputDevice() +{ + close(); +} + +bool AndroidInputDevice::open() +{ + if (active()) + return true; + + oboe::AudioStreamBuilder builder; + builder.setDirection(oboe::Direction::Input); + builder.setPerformanceMode(oboe::PerformanceMode::LowLatency); + builder.setSharingMode(oboe::SharingMode::Exclusive); + builder.setFormat(oboe::AudioFormat::I16); + builder.setChannelCount(oboe::ChannelCount::Mono); + builder.setCallback(this); + oboe::Result rescode = builder.openStream(&mRecordingStream); + if (rescode != oboe::Result::OK) + return false; + + mDeviceRate = mRecordingStream->getSampleRate(); + ICELogInfo(<< "Input Opened with rate " << mDeviceRate); + mActive = true; + + rescode = mRecordingStream->requestStart(); + if (rescode != oboe::Result::OK) + { + close(); + mActive = false; + } + return mActive; +} + +void AndroidInputDevice::close() +{ + // There is no check for active() value because close() can be called to cleanup after bad open() call. + if (mRecordingStream != nullptr) + { + mRecordingStream->close(); + delete mRecordingStream; mRecordingStream = nullptr; + } + mActive = false; +} + +oboe::DataCallbackResult +AndroidInputDevice::onAudioReady(oboe::AudioStream *audioStream, void *audioData, int32_t numFrames) +{ + std::unique_lock l(mMutex); + + // Send data to AudioPair + if (mConnection) + mConnection->onMicData(getFormat(), audioData, numFrames); + + return oboe::DataCallbackResult::Continue; +} + +Format AndroidInputDevice::getFormat() +{ + return Format(mDeviceRate, 1); +} + +bool AndroidInputDevice::active() const +{ + return mActive; +} + +bool AndroidInputDevice::fakeMode() +{ + return false; +} + +void AndroidInputDevice::setFakeMode(bool fakemode) +{} + +int AndroidInputDevice::readBuffer(void* buffer) +{ + throw std::runtime_error("AndroidInputDevice::readBuffer() is not implemented."); +} + +// ------------ AndroidOutputDevice ----------------- +AndroidOutputDevice::AndroidOutputDevice(int devId) +{ + ICELogDebug(<< "Creating AndroidOutputDevice. This is: " << StringHelper::toHex(this)); +} +AndroidOutputDevice::~AndroidOutputDevice() +{ + ICELogDebug(<< "Deleting AndroidOutputDevice."); + close(); +} + +bool AndroidOutputDevice::open() +{ + std::unique_lock l(mMutex); + + if (mActive) + return true; + + mRequestedFrames = 0; + mStartTime = 0.0; + mEndTime = 0.0; + + oboe::AudioStreamBuilder builder; + builder.setDirection(oboe::Direction::Output); + builder.setPerformanceMode(oboe::PerformanceMode::LowLatency); + builder.setSharingMode(oboe::SharingMode::Exclusive); + builder.setFormat(oboe::AudioFormat::I16); + builder.setChannelCount(oboe::ChannelCount::Mono); + // builder.setDataCallback(this); + builder.setCallback(this); + //builder.setErrorCallback(this) + + oboe::Result rescode = builder.openStream(&mPlayingStream); + if (rescode != oboe::Result::OK) + return false; + + mDeviceRate = mPlayingStream->getSampleRate(); + ICELogInfo(<< "Input Opened with rate " << mDeviceRate); + mActive = true; + + rescode = mPlayingStream->requestStart(); + if (rescode != oboe::Result::OK) + { + close(); + mActive = false; + } + return mActive; +} + +void AndroidOutputDevice::close() +{ + std::unique_lock l(mMutex); + if (!mActive) + return; + + if (mPlayingStream != nullptr) + { + mPlayingStream->close(); + delete mPlayingStream; mPlayingStream = nullptr; + } + mEndTime = now_ms(); + mActive = false; + + ICELogInfo(<< "For time " << mEndTime - mStartTime << " ms was requested " + << float(mRequestedFrames) / getFormat().mRate * 1000 << " ms"); +} + +Format AndroidOutputDevice::getFormat() +{ + return {mDeviceRate, 1}; +} + +bool AndroidOutputDevice::fakeMode() +{ + return false; +} + +void AndroidOutputDevice::setFakeMode(bool /*fakemode*/) +{ +} + +oboe::DataCallbackResult AndroidOutputDevice::onAudioReady(oboe::AudioStream *audioStream, void *audioData, int32_t numFrames) +{ + if (mInShutdown) + return oboe::DataCallbackResult::Stop; + + if (mStartTime == 0.0) + mStartTime = now_ms(); + + // Ask producer about data + memset(audioData, 0, numFrames * 2); + if (mConnection) + { + Format f = getFormat(); + if (f.mRate != 0) + mConnection->onSpkData(f, audioData, numFrames * 2); + } + mRequestedFrames += numFrames; + + return oboe::DataCallbackResult::Continue; +} + +// TODO - special case https://github.com/google/oboe/blob/master/docs/notes/disconnect.md +void AndroidOutputDevice::onErrorAfterClose(oboe::AudioStream *stream, oboe::Result result) { + if (result == oboe::Result::ErrorDisconnected) { + // LOGI("Restarting AudioStream after disconnect"); + // soundEngine.restart(); // please check oboe samples for soundEngine.restart(); call + } +} +#endif // TARGET_ANDROID \ No newline at end of file diff --git a/src/engine/audio/Audio_AndroidOboe.h b/src/engine/audio/Audio_AndroidOboe.h new file mode 100644 index 00000000..b67e74f5 --- /dev/null +++ b/src/engine/audio/Audio_AndroidOboe.h @@ -0,0 +1,109 @@ +/* Copyright(C) 2007-2017 VoIP objects (voipobjects.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +#ifndef __AUDIO_ANDROID_OBOE_H +#define __AUDIO_ANDROID_OBOE_H + +#ifdef TARGET_ANDROID + +#include "Audio_Interface.h" +#include "Audio_Helper.h" +#include "Audio_Resampler.h" +#include "Audio_DataWindow.h" +#include "../helper/HL_Pointer.h" +#include "../helper/HL_ByteBuffer.h" +#include "../helper/HL_Exception.h" +#include "../helper/HL_Statistics.h" + +#include +#include + +#include "oboe/Oboe.h" + +namespace Audio +{ + class AndroidEnumerator: public Enumerator + { + public: + AndroidEnumerator(); + ~AndroidEnumerator(); + + void open(int direction); + void close(); + + int count(); + std::string nameAt(int index); + int idAt(int index); + int indexOfDefaultDevice(); + + protected: + }; + + class AndroidInputDevice: public InputDevice, public oboe::AudioStreamCallback + { + public: + AndroidInputDevice(int devId); + ~AndroidInputDevice(); + + bool open(); + void close(); + Format getFormat(); + + bool fakeMode(); + void setFakeMode(bool fakemode); + int readBuffer(void* buffer); + bool active() const; + + oboe::DataCallbackResult + onAudioReady(oboe::AudioStream *audioStream, void *audioData, int32_t numFrames); + + + protected: + bool mActive = false; + oboe::AudioStream* mRecordingStream = nullptr; + PResampler mResampler; + DataWindow mDeviceRateCache, mSdkRateCache; + int mDeviceRate; // Actual rate of opened recorder + int mBufferSize; // Size of buffer used for recording (at native sample rate) + DataWindow mRecorderBuffer; + std::condition_variable mDataCondVar; + int mRecorderBufferIndex; + std::mutex mMutex; + }; + +class AndroidOutputDevice: public OutputDevice, public oboe::AudioStreamCallback + { + public: + AndroidOutputDevice(int devId); + ~AndroidOutputDevice(); + + bool open(); + void close(); + Format getFormat(); + + bool fakeMode(); + void setFakeMode(bool fakemode); + + oboe::DataCallbackResult onAudioReady(oboe::AudioStream *audioStream, void *audioData, int32_t numFrames); + void onErrorAfterClose(oboe::AudioStream *stream, oboe::Result result); + + protected: + std::mutex mMutex; + int mDeviceRate = 0; + oboe::AudioStream* mPlayingStream = nullptr; + DataWindow mPlayBuffer; + int mBufferIndex = 0, mBufferSize = 0; + bool mInShutdown = false; + bool mActive = false; + + // Statistics + float mRequestedFrames = 0.0, mStartTime = 0.0, mEndTime = 0.0; + }; +} + +#endif // TARGET_ANDROID + +#endif // __AUDIO_ANDROID_H diff --git a/src/engine/audio/Audio_DataWindow.cpp b/src/engine/audio/Audio_DataWindow.cpp index 1b0c1f2a..6044ff78 100644 --- a/src/engine/audio/Audio_DataWindow.cpp +++ b/src/engine/audio/Audio_DataWindow.cpp @@ -56,17 +56,25 @@ void DataWindow::add(const void* data, int length) if (length > mCapacity) { + // Use latest bytes from data buffer in this case. data = (char*)data + length - mCapacity; length = mCapacity; } + // Check how much free space we have int avail = mCapacity - mFilled; - + if (avail < length) { - memmove(mData, mData + length - avail, mFilled - (length - avail)); - mFilled -= length - avail; + // Find the portion of data to move & save + int delta = length - avail; + + // Move the data + if (mFilled - delta > 0) + memmove(mData, mData + delta, mFilled - delta); + mFilled -= delta; } + memcpy(mData + mFilled, data, length); mFilled += length; } diff --git a/src/engine/audio/Audio_DevicePair.cpp b/src/engine/audio/Audio_DevicePair.cpp index 45c2bc65..94ce5266 100644 --- a/src/engine/audio/Audio_DevicePair.cpp +++ b/src/engine/audio/Audio_DevicePair.cpp @@ -197,7 +197,7 @@ void DevicePair::onSpkData(const Format& f, void* buffer, int length) // Resample these 10 milliseconds it to native format size_t wasProcessed = 0; - size_t wasProduced = mSpkResampler.resample(AUDIO_SAMPLERATE, mOutput10msBuffer.data(), mOutput10msBuffer.capacity(), wasProcessed, f.mRate, + size_t wasProduced = mSpkResampler.resample(nativeFormat.mRate, mOutput10msBuffer.data(), mOutput10msBuffer.capacity(), wasProcessed, f.mRate, mOutputNativeData.mutableData() + mOutputNativeData.filled(), mOutputNativeData.capacity() - mOutputNativeData.filled()); mOutputNativeData.setFilled(mOutputNativeData.filled() + wasProduced); #ifdef CONSOLE_LOGGING @@ -206,7 +206,7 @@ void DevicePair::onSpkData(const Format& f, void* buffer, int length) } } - assert(mOutputNativeData.filled() >= length); + // assert(mOutputNativeData.filled() >= length); #ifdef DUMP_NATIVEOUTPUT if (mNativeOutputDump) mNativeOutputDump->write(mOutputNativeData.data(), length); diff --git a/src/engine/audio/Audio_Interface.cpp b/src/engine/audio/Audio_Interface.cpp index e1844c95..3d0abf72 100644 --- a/src/engine/audio/Audio_Interface.cpp +++ b/src/engine/audio/Audio_Interface.cpp @@ -146,7 +146,8 @@ OsEngine* OsEngine::instance() #endif #ifdef TARGET_ANDROID - return &OpenSLEngine::instance(); + return nullptr; // As we use Oboe library for now + //return &OpenSLEngine::instance(); #endif return nullptr; diff --git a/src/engine/audio/Audio_Mixer.cpp b/src/engine/audio/Audio_Mixer.cpp index 5e4aca7c..d6274206 100644 --- a/src/engine/audio/Audio_Mixer.cpp +++ b/src/engine/audio/Audio_Mixer.cpp @@ -216,48 +216,52 @@ void Mixer::mix() channelList[activeCounter++] = &mChannelList[i]; // No active channels - nothing to mix - exit - if (!activeCounter) + if (!activeCounter) { - //ICELogDebug(<< "No active channel"); + // ICELogDebug(<< "No active channel"); return; } // Optimized versions for 1& 2 active channels if (activeCounter == 1) - { - // Copy much samples as we have + { + // Copy much samples as we have Stream& audio = *channelList[0]; - mOutput.add(audio.data().data(), audio.data().filled()); - audio.data().erase(audio.data().filled()); + + // Copy the decoded data + mOutput.add(audio.data().data(), audio.data().filled()); + + // Erase copied audio samples + audio.data().erase(audio.data().filled()); //ICELogSpecial(<<"Length of mixer stream " << audio.data().filled()); - } + } else - if (activeCounter == 2) - { + if (activeCounter == 2) + { Stream& audio1 = *channelList[0]; - Stream& audio2 = *channelList[1]; - int filled1 = audio1.data().filled() / 2, filled2 = audio2.data().filled() / 2; + Stream& audio2 = *channelList[1]; + int filled1 = audio1.data().filled() / 2, filled2 = audio2.data().filled() / 2; int available = filled1 > filled2 ? filled1 : filled2; - // Find how much samples can be mixed - int filled = mOutput.filled() / 2; + // Find how much samples can be mixed + int filled = mOutput.filled() / 2; int maxsize = mOutput.capacity() / 2; - if (maxsize - filled < available) - available = maxsize - filled; + if (maxsize - filled < available) + available = maxsize - filled; - short sample = 0; - for (int i=0; i i ? audio1.data().shortAt(i) : 0; short sample2 = filled2 > i ? audio2.data().shortAt(i) : 0; sample = (abs(sample1) > abs(sample2)) ? sample1 : sample2; mOutput.add(sample); - } - audio1.data().erase(available*2); - audio2.data().erase(available*2); } + audio1.data().erase(available*2); + audio2.data().erase(available*2); + } else { do diff --git a/src/engine/audio/Audio_Player.cpp b/src/engine/audio/Audio_Player.cpp index cdb1952d..7aaa22ee 100644 --- a/src/engine/audio/Audio_Player.cpp +++ b/src/engine/audio/Audio_Player.cpp @@ -1,14 +1,18 @@ -/* Copyright(C) 2007-2014 VoIP objects (voipobjects.com) +/* Copyright(C) 2007-2021 VoIP objects (voipobjects.com) * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "Audio_Player.h" +#include "../helper/HL_Log.h" + +#define LOG_SUBSYSTEM "Player" + using namespace Audio; // -------------- Player ----------- Player::Player() -:mDelegate(NULL), mPlayedTime(0) +:mDelegate(nullptr), mPlayedTime(0) { } @@ -47,7 +51,7 @@ void Player::onMicData(const Format& f, const void* buffer, int length) void Player::onSpkData(const Format& f, void* buffer, int length) { Lock l(mGuard); - + // Fill buffer by zero if player owns dedicated device if (mOutput) memset(buffer, 0, length); @@ -99,7 +103,7 @@ void Player::onFilePlayed() void Player::obtain(int usage) { Lock l(mGuard); - UsageMap::iterator usageIter = mUsage.find(usage); + auto usageIter = mUsage.find(usage); if (usageIter == mUsage.end()) mUsage[usage] = 1; else @@ -132,7 +136,7 @@ int Player::releasePlayed() { Lock l(mGuard); int result = mFinishedUsages.size(); - while (mFinishedUsages.size()) + while (!mFinishedUsages.empty()) { release(mFinishedUsages.front()); mFinishedUsages.erase(mFinishedUsages.begin()); diff --git a/src/engine/audio/Audio_Player.h b/src/engine/audio/Audio_Player.h index 614855af..82656d8c 100644 --- a/src/engine/audio/Audio_Player.h +++ b/src/engine/audio/Audio_Player.h @@ -1,4 +1,4 @@ -/* Copyright(C) 2007-2014 VoIP objects (voipobjects.com) +/* Copyright(C) 2007-2021 VoIP objects (voipobjects.com) * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ @@ -8,6 +8,7 @@ #include "../helper/HL_Log.h" #include "../helper/HL_Sync.h" +#include "../helper/HL_Statistics.h" #include "Audio_Interface.h" #include #include @@ -48,15 +49,18 @@ namespace Audio void onMicData(const Format& f, const void* buffer, int length); void onSpkData(const Format& f, void* buffer, int length); void onFilePlayed(); - void scheduleRelease(); void obtain(int usageId); + public: Player(); ~Player(); + void setDelegate(EndOfAudioDelegate* d); EndOfAudioDelegate* getDelegate() const; + void setOutput(POutputDevice output); POutputDevice getOutput() const; + void add(int usageId, PWavFileReader file, bool loop, int timelength); void release(int usageId); void clear(); diff --git a/src/engine/config.h b/src/engine/config.h index b1390648..0d84f550 100644 --- a/src/engine/config.h +++ b/src/engine/config.h @@ -17,7 +17,7 @@ #define AUDIO_CHANNELS 1 // Samplerate must be 8 / 16 / 24 / 32 / 48 KHz -#define AUDIO_SAMPLERATE 16000 +#define AUDIO_SAMPLERATE 8000 #define AUDIO_MIC_BUFFER_COUNT 16 #define AUDIO_MIC_BUFFER_LENGTH 10 #define AUDIO_MIC_BUFFER_SIZE (AUDIO_MIC_BUFFER_LENGTH * AUDIO_SAMPLERATE / 1000 * 2 * AUDIO_CHANNELS) @@ -50,7 +50,7 @@ #define MT_MAXRTPPACKET 1500 #define MT_DTMF_END_PACKETS 3 -#define RTP_BUFFER_HIGH 480 +#define RTP_BUFFER_HIGH 24480 #define RTP_BUFFER_LOW 10 #define RTP_BUFFER_PREBUFFER 80 #define RTP_DECODED_CAPACITY 2048 @@ -66,15 +66,15 @@ // AMR codec defines - it requires USE_AMR_CODEC defined // #define USE_AMR_CODEC -#define MT_AMRNB_PAYLOADTYPE 122 +#define MT_AMRNB_PAYLOADTYPE 112 #define MT_AMRNB_CODECNAME "amr" -#define MT_AMRNB_OCTET_PAYLOADTYPE 123 +#define MT_AMRNB_OCTET_PAYLOADTYPE 113 -#define MT_AMRWB_PAYLOADTYPE 124 +#define MT_AMRWB_PAYLOADTYPE 96 #define MT_AMRWB_CODECNAME "amr-wb" -#define MT_AMRWB_OCTET_PAYLOADTYPE 125 +#define MT_AMRWB_OCTET_PAYLOADTYPE 97 #define MT_GSMEFR_PAYLOADTYPE 126 #define MT_GSMEFR_CODECNAME "GERAN-EFR" diff --git a/src/engine/endpoint/EP_AudioProvider.h b/src/engine/endpoint/EP_AudioProvider.h index b5968cbd..cb2388f6 100644 --- a/src/engine/endpoint/EP_AudioProvider.h +++ b/src/engine/endpoint/EP_AudioProvider.h @@ -28,19 +28,19 @@ public: virtual ~AudioProvider(); // Returns provider RTP name - std::string streamName(); + std::string streamName() override; // Returns provider RTP profile name - std::string streamProfile(); + std::string streamProfile() override; // Sets destination IP address - void setDestinationAddress(const RtpPair& addr); + void setDestinationAddress(const RtpPair& addr) override; // Processes incoming data - void processData(PDatagramSocket s, const void* dataBuffer, int dataSize, InternetAddress& source); + void processData(PDatagramSocket s, const void* dataBuffer, int dataSize, InternetAddress& source) override; // This method is called by user agent to send ICE packet from mediasocket - void sendData(PDatagramSocket s, InternetAddress& destination, const void* dataBuffer, unsigned int datasize); + void sendData(PDatagramSocket s, InternetAddress& destination, const void* dataBuffer, unsigned int datasize) override; // Updates SDP offer void updateSdpOffer(resip::SdpContents::Session::Medium& sdp, SdpDirection direction) override; diff --git a/src/engine/endpoint/EP_Engine.cpp b/src/engine/endpoint/EP_Engine.cpp index 6ca93e14..b2723604 100644 --- a/src/engine/endpoint/EP_Engine.cpp +++ b/src/engine/endpoint/EP_Engine.cpp @@ -93,7 +93,7 @@ void UserAgent::start() } // Initialize resip loggег - resip::Log::initialize(resip::Log::OnlyExternal, resip::Log::Info, "Client", *this); + resip::Log::initialize(resip::Log::OnlyExternal, resip::Log::Debug, "Client", *this); // Build list of nameservers if specified resip::DnsStub::NameserverList nslist; @@ -151,7 +151,7 @@ void UserAgent::start() switch (mConfig[CONFIG_TRANSPORT].asInt()) { - case 0: + case TransportType_Any: if (mConfig[CONFIG_IPV4].asBool()) { ADD_TRANSPORT4(resip::TCP) @@ -166,21 +166,21 @@ void UserAgent::start() } break; - case 1: + case TransportType_Udp: if (mConfig[CONFIG_IPV4].asBool()) ADD_TRANSPORT4(resip::UDP); if (mConfig[CONFIG_IPV6].asBool()) ADD_TRANSPORT6(resip::UDP); break; - case 2: + case TransportType_Tcp: if (mConfig[CONFIG_IPV4].asBool()) ADD_TRANSPORT4(resip::TCP); if (mConfig[CONFIG_IPV6].asBool()) ADD_TRANSPORT6(resip::TCP); break; - case 3: + case TransportType_Tls: if (mConfig[CONFIG_IPV4].asBool()) ADD_TRANSPORT4(resip::TLS); if (mConfig[CONFIG_IPV6].asBool()) diff --git a/src/engine/endpoint/EP_Engine.h b/src/engine/endpoint/EP_Engine.h index fde22efd..7d1cba5b 100644 --- a/src/engine/endpoint/EP_Engine.h +++ b/src/engine/endpoint/EP_Engine.h @@ -60,6 +60,14 @@ #define RESIPROCATE_SUBSYSTEM Subsystem::TEST using namespace std; +enum +{ + TransportType_Any, + TransportType_Udp, + TransportType_Tcp, + TransportType_Tls +}; + enum { CONFIG_IPV4 = 0, // Use IP4 diff --git a/src/engine/helper/HL_NetworkFrame.cpp b/src/engine/helper/HL_NetworkFrame.cpp index c7cdc1e5..fc410d66 100644 --- a/src/engine/helper/HL_NetworkFrame.cpp +++ b/src/engine/helper/HL_NetworkFrame.cpp @@ -25,10 +25,15 @@ NetworkFrame::PacketData NetworkFrame::GetUdpPayloadForEthernet(NetworkFrame::Pa uint16_t proto = 0; if (ethernet->mEtherType == 129) { - const VlanHeader* vlan = reinterpret_cast(packet.mData); - packet.mData += sizeof(VlanHeader); - packet.mLength -= sizeof(VlanHeader); - proto = ntohs(vlan->mData); + // Skip 1 or more VLAN headers + do + { + const VlanHeader* vlan = reinterpret_cast(packet.mData); + packet.mData += sizeof(VlanHeader); + packet.mLength -= sizeof(VlanHeader); + proto = ntohs(vlan->mData); + } + while (proto == 0x8100); } // Skip MPLS headers diff --git a/src/engine/helper/HL_Rtp.cpp b/src/engine/helper/HL_Rtp.cpp index b7594daf..ec09814c 100644 --- a/src/engine/helper/HL_Rtp.cpp +++ b/src/engine/helper/HL_Rtp.cpp @@ -245,6 +245,17 @@ std::string MediaStreamId::getFinishDescription() const return oss.str(); } +MediaStreamId& MediaStreamId::operator = (const MediaStreamId& src) +{ + this->mDestination = src.mDestination; + this->mSource = src.mSource; + this->mLinkId = src.mLinkId; + this->mSSRC = src.mSSRC; + this->mSsrcIsId = src.mSsrcIsId; + + return *this; +} + std::ostream& operator << (std::ostream& output, const MediaStreamId& id) { return (output << id.toString()); diff --git a/src/engine/helper/HL_Rtp.h b/src/engine/helper/HL_Rtp.h index 9d6c81d9..8df1f3b5 100644 --- a/src/engine/helper/HL_Rtp.h +++ b/src/engine/helper/HL_Rtp.h @@ -85,6 +85,7 @@ struct MediaStreamId std::string toString() const; std::string getDetectDescription() const; std::string getFinishDescription() const; + MediaStreamId& operator = (const MediaStreamId& src); }; std::ostream& operator << (std::ostream& output, const MediaStreamId& id); diff --git a/src/engine/helper/HL_Statistics.cpp b/src/engine/helper/HL_Statistics.cpp new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/engine/helper/HL_Statistics.cpp @@ -0,0 +1 @@ + diff --git a/src/engine/helper/HL_Statistics.h b/src/engine/helper/HL_Statistics.h new file mode 100644 index 00000000..a9f29cef --- /dev/null +++ b/src/engine/helper/HL_Statistics.h @@ -0,0 +1,82 @@ +#ifndef __HELPER_STATISTICS_H +#define __HELPER_STATISTICS_H + +template +struct Average +{ + int mCount = 0; + T mSum = 0; + T average() const + { + if (!mCount) + return 0; + return mSum / mCount; + } + + T value() const + { + return average(); + } + + void process(T value) + { + mCount++; + mSum += value; + } +}; + +template +struct TestResult +{ + T mMin = minimum; + T mMax = maximum; + Average mAverage; + T mCurrent = default_value; + + void process(T value) + { + if (mMin > value) + mMin = value; + if (mMax < value) + mMax = value; + mCurrent = value; + mAverage.process(value); + } + + bool is_initialized() const + { + return mAverage.mCount > 0; + } + + T current() const + { + if (is_initialized()) + return mCurrent; + else + return 0; + } + + T value() const + { + return current(); + } + + T average() const + { + return mAverage.average(); + } + + TestResult& operator = (T value) + { + process(value); + return *this; + } + + operator T() + { + return mCurrent; + } +}; + + +#endif \ No newline at end of file diff --git a/src/engine/helper/HL_Time.cpp b/src/engine/helper/HL_Time.cpp new file mode 100644 index 00000000..9bfa995f --- /dev/null +++ b/src/engine/helper/HL_Time.cpp @@ -0,0 +1,10 @@ +#include "HL_Time.h" + +#include + +/* return current time in milliseconds */ +double now_ms(void) { + struct timespec res; + clock_gettime(CLOCK_MONOTONIC, &res); + return 1000.0 * res.tv_sec + (double) res.tv_nsec / 1e6; +} \ No newline at end of file diff --git a/src/engine/helper/HL_Time.h b/src/engine/helper/HL_Time.h new file mode 100644 index 00000000..639dd49f --- /dev/null +++ b/src/engine/helper/HL_Time.h @@ -0,0 +1,6 @@ +#ifndef __HELPER_TIME_H +#define __HELPER_TIME_H + +extern double now_ms(); + +#endif diff --git a/src/engine/media/MT_AudioReceiver.cpp b/src/engine/media/MT_AudioReceiver.cpp index a40c63df..cc677731 100644 --- a/src/engine/media/MT_AudioReceiver.cpp +++ b/src/engine/media/MT_AudioReceiver.cpp @@ -10,6 +10,7 @@ #include "MT_AudioCodec.h" #include "MT_CngHelper.h" #include "../helper/HL_Log.h" +#include "../helper/HL_Time.h" #include "../audio/Audio_Interface.h" #include "../audio/Audio_Resampler.h" #include @@ -47,11 +48,19 @@ int RtpBuffer::Packet::rate() const return mRate; } +const std::vector& RtpBuffer::Packet::pcm() const +{ + return mPcm; +} + +std::vector& RtpBuffer::Packet::pcm() +{ + return mPcm; +} + // ------------ RtpBuffer ---------------- RtpBuffer::RtpBuffer(Statistics& stat) - :mStat(stat), mSsrc(0), mHigh(RTP_BUFFER_HIGH), mLow(RTP_BUFFER_LOW), mPrebuffer(RTP_BUFFER_PREBUFFER), - mFirstPacketWillGo(true), mReturnedCounter(0), mAddCounter(0), - mFetchedPacket(std::shared_ptr(), 0, 0) + :mStat(stat) { } @@ -96,19 +105,28 @@ int RtpBuffer::getCount() const return static_cast(mPacketList.size()); } -bool SequenceSort(const RtpBuffer::Packet& p1, const RtpBuffer::Packet& p2) +bool SequenceSort(const std::shared_ptr& p1, const std::shared_ptr& p2) { - return p1.rtp()->GetExtendedSequenceNumber() < p2.rtp()->GetExtendedSequenceNumber(); + return p1->rtp()->GetExtendedSequenceNumber() < p2->rtp()->GetExtendedSequenceNumber(); } -bool RtpBuffer::add(std::shared_ptr packet, int timelength, int rate) +std::shared_ptr RtpBuffer::add(std::shared_ptr packet, int timelength, int rate) { if (!packet) - return false; + return std::shared_ptr(); Lock l(mGuard); + // Update statistics + if (mLastAddTime == 0.0) + mLastAddTime = now_ms(); + else + { + float t = now_ms(); + mStat.mPacketInterval.process(t - mLastAddTime); + mLastAddTime = t; + } mStat.mSsrc = static_cast(packet->GetSSRC()); // Update jitter @@ -121,15 +139,15 @@ bool RtpBuffer::add(std::shared_ptr packet, int timelength, // New sequence number unsigned newSeqno = packet->GetExtendedSequenceNumber(); - for (Packet& p: mPacketList) + for (std::shared_ptr& p: mPacketList) { - unsigned seqno = p.rtp()->GetExtendedSequenceNumber(); + unsigned seqno = p->rtp()->GetExtendedSequenceNumber(); if (seqno == newSeqno) { mStat.mDuplicatedRtp++; ICELogMedia(<< "Discovered duplicated packet, skipping"); - return false; + return std::shared_ptr(); } if (seqno > maxno) @@ -143,8 +161,11 @@ bool RtpBuffer::add(std::shared_ptr packet, int timelength, if (newSeqno > minno || (available < mHigh)) { - Packet p(packet, timelength, rate); + // Insert into queue + auto p = std::make_shared(packet, timelength, rate); mPacketList.push_back(p); + + // Sort again std::sort(mPacketList.begin(), mPacketList.end(), SequenceSort); // Limit by max timelength @@ -152,21 +173,18 @@ bool RtpBuffer::add(std::shared_ptr packet, int timelength, if (available > mHigh) ICELogMedia(<< "Available " << available << "ms with limit " << mHigh << "ms"); - /*while (available > mHigh && mPacketList.size()) - { - ICELogDebug( << "Dropping RTP packet from jitter buffer"); - available -= mPacketList.front().timelength(); - mPacketList.erase(mPacketList.begin()); - }*/ + + return p; } else { ICELogMedia(<< "Too old packet, skipping"); mStat.mOldRtp++; - return false; + + return std::shared_ptr(); } - return true; + return std::shared_ptr(); } RtpBuffer::FetchResult RtpBuffer::fetch(ResultList& rl) @@ -182,7 +200,7 @@ RtpBuffer::FetchResult RtpBuffer::fetch(ResultList& rl) while (total > mHigh && mPacketList.size()) { ICELogMedia( << "Dropping RTP packets from jitter buffer"); - total -= mPacketList.front().timelength(); + total -= mPacketList.front()->timelength(); // Save it as last packet however - to not confuse loss packet counter mFetchedPacket = mPacketList.front(); @@ -198,7 +216,11 @@ RtpBuffer::FetchResult RtpBuffer::fetch(ResultList& rl) result = FetchResult::NoPacket; else { - if (mFetchedPacket.rtp()) + bool is_fetched_packet = mFetchedPacket.get() != nullptr; + if (is_fetched_packet) + is_fetched_packet &= mFetchedPacket->rtp().get() != nullptr; + + if (is_fetched_packet) { if (mPacketList.empty()) { @@ -208,10 +230,10 @@ RtpBuffer::FetchResult RtpBuffer::fetch(ResultList& rl) else { // Current sequence number ? - unsigned seqno = mPacketList.front().rtp()->GetExtendedSequenceNumber(); + unsigned seqno = mPacketList.front()->rtp()->GetExtendedSequenceNumber(); // Gap between new packet and previous on - int gap = seqno - mFetchedPacket.rtp()->GetSequenceNumber() - 1; + int gap = seqno - mFetchedPacket->rtp()->GetSequenceNumber() - 1; gap = std::min(gap, 127); if (gap > 0 && mPacketList.empty()) { @@ -228,29 +250,7 @@ RtpBuffer::FetchResult RtpBuffer::fetch(ResultList& rl) } result = FetchResult::RegularPacket; - Packet& p = mPacketList.front(); - rl.push_back(p.rtp()); - - // Maybe it is time to replay packet right now ? For case of AMR SID packets - /*if (mFetchedPacket.rtp() && gap == 0 && mFetchedPacket.timelength() >= 10 && p.timelength() >= 10) - { - int timestampDelta; - // Timestamp difference - if (p.rtp()->GetTimestamp() > mFetchedPacket.rtp()->GetTimestamp()) - timestampDelta = TimeHelper::getDelta(p.rtp()->GetTimestamp(), mFetchedPacket.rtp()->GetTimestamp()); - else - timestampDelta = TimeHelper::getDelta(mFetchedPacket.rtp()->GetTimestamp(), p.rtp()->GetTimestamp()); - - // Timestamp units per packet - int nrOfPackets = timestampDelta / (p.timelength() * (p.rate() / 1000)); - - // Add more copies of SID (most probably) packets - for (int i = 0; i < nrOfPackets - 1; i++) - { - //assert(false); - rl.push_back(p.rtp()); - } - }*/ + rl.push_back(mPacketList.front()); // Save last returned normal packet mFetchedPacket = mPacketList.front(); @@ -269,7 +269,7 @@ RtpBuffer::FetchResult RtpBuffer::fetch(ResultList& rl) result = FetchResult::RegularPacket; // Put it to output list - rl.push_back(mPacketList.front().rtp()); + rl.push_back(mPacketList.front()); // Remember returned packet mFetchedPacket = mPacketList.front(); @@ -295,7 +295,7 @@ int RtpBuffer::findTimelength() { int available = 0; for (unsigned i = 0; i < mPacketList.size(); i++) - available += mPacketList[i].timelength(); + available += mPacketList[i]->timelength(); return available; } @@ -321,7 +321,7 @@ Receiver::~Receiver() //-------------- AudioReceiver ---------------- AudioReceiver::AudioReceiver(const CodecList::Settings& settings, MT::Statistics &stat) - :Receiver(stat), mBuffer(stat), mFrameCount(0), mFailedCount(0), mCodecSettings(settings), + :Receiver(stat), mBuffer(stat), mCodecSettings(settings), mCodecList(settings) { // Init resamplers @@ -354,9 +354,45 @@ AudioReceiver::~AudioReceiver() mDecodedDump.reset(); } +size_t decode_packet(Codec& codec, RTPPacket& p, void* output_buffer, size_t output_capacity) +{ + // How much data was produced + size_t result = 0; + + // Handle here regular RTP packets + // Check if payload length is ok + int tail = codec.rtpLength() ? p.GetPayloadLength() % codec.rtpLength() : 0; + + if (!tail) + { + // Find number of frames + int frame_count = codec.rtpLength() ? p.GetPayloadLength() / codec.rtpLength() : 1; + int frame_length = codec.rtpLength() ? codec.rtpLength() : (int)p.GetPayloadLength(); + + // Save last packet time length + // mLastPacketTimeLength = mFrameCount * mCodec->frameTime(); + + // Decode + + for (int i=0; i < frame_count; i++) + { + auto decoded_length = codec.decode(p.GetPayloadData() + i * codec.rtpLength(), + frame_length, + output_buffer, + output_capacity); + + result += decoded_length; + } + } + else + ICELogMedia(<< "RTP packet with tail."); + + return result; +} bool AudioReceiver::add(const std::shared_ptr& p, Codec** codec) { + // ICELogInfo(<< "Adding packet No " << p->GetSequenceNumber()); // Increase codec counter mStat.mCodecCount[p->GetPayloadType()]++; @@ -410,7 +446,22 @@ bool AudioReceiver::add(const std::shared_ptr& p, Codec** co } // Queue packet to buffer - return mBuffer.add(p, time_length, codecIter->second->samplerate()); + auto packet = mBuffer.add(p, time_length, codecIter->second->samplerate()).get(); + + if (packet) + { + // Check if early decoding configured + if (mEarlyDecode && *codec) + { + // Move data to packet buffer + size_t available = decode_packet(**codec, *p, mDecodedFrame, sizeof mDecodedFrame); + packet->pcm().resize(available / 2); + memcpy(packet->pcm().data(), mDecodedFrame, available / 2); + } + return true; + } + else + return false; } void AudioReceiver::processDecoded(Audio::DataWindow& output, int options) @@ -435,7 +486,7 @@ void AudioReceiver::processDecoded(Audio::DataWindow& output, int options) bool AudioReceiver::getAudio(Audio::DataWindow& output, int options, int* rate) { - bool result = false; + bool result = false, /*had_cng = false, */had_decode = false; // Get next packet from buffer RtpBuffer::ResultList rl; @@ -443,7 +494,7 @@ bool AudioReceiver::getAudio(Audio::DataWindow& output, int options, int* rate) switch (fr) { case RtpBuffer::FetchResult::Gap: - ICELogInfo(<< "Gap detected."); + ICELogDebug(<< "Gap detected."); mDecodedLength = mResampledLength = 0; if (mCngPacket && mCodec) @@ -482,14 +533,14 @@ bool AudioReceiver::getAudio(Audio::DataWindow& output, int options, int* rate) case RtpBuffer::FetchResult::RegularPacket: mFailedCount = 0; - for (std::shared_ptr& p: rl) + for (std::shared_ptr& p: rl) { assert(p); // Check if previously CNG packet was detected. Emit CNG audio here if needed. if (options & DecodeOptions_FillCngGap && mCngPacket && mCodec) { // Fill CNG audio is server mode is present - int units = p->GetTimestamp() - mCngPacket->GetTimestamp(); + int units = p->rtp()->GetTimestamp() - mCngPacket->GetTimestamp(); int milliseconds = units / (mCodec->samplerate() / 1000); if (milliseconds > mLastPacketTimeLength) { @@ -522,69 +573,83 @@ bool AudioReceiver::getAudio(Audio::DataWindow& output, int options, int* rate) } } - // Find codec - mCodec = mCodecMap[p->GetPayloadType()]; - if (mCodec) + if (mEarlyDecode) { - if (rate) - *rate = mCodec->samplerate(); + // ToDo - copy the decoded data to output buffer - // Check if it is CNG packet - if ((p->GetPayloadType() == 0 || p->GetPayloadType() == 8) && p->GetPayloadLength() >= 1 && p->GetPayloadLength() <= 6) + } + else + { + // Find codec by payload type + int ptype = p->rtp()->GetPayloadType(); + mCodec = mCodecMap[ptype]; + if (mCodec) { - if (options & DecodeOptions_SkipDecode) - mDecodedLength = 0; - else + if (rate) + *rate = mCodec->samplerate(); + + // Check if it is CNG packet + if ((ptype == 0 || ptype == 8) && p->rtp()->GetPayloadLength() >= 1 && p->rtp()->GetPayloadLength() <= 6) { - mCngPacket = p; - mCngDecoder.decode3389(p->GetPayloadData(), p->GetPayloadLength()); - // Emit CNG mLastPacketLength milliseconds - mDecodedLength = mCngDecoder.produce(mCodec->samplerate(), mLastPacketTimeLength, - (short*)mDecodedFrame, true); - if (mDecodedLength) - processDecoded(output, options); - } - result = true; - } - else - { - // Reset CNG packet - mCngPacket.reset(); - - // Handle here regular RTP packets - // Check if payload length is ok - int tail = mCodec->rtpLength() ? p->GetPayloadLength() % mCodec->rtpLength() : 0; - - if (!tail) - { - // Find number of frames - mFrameCount = mCodec->rtpLength() ? p->GetPayloadLength() / mCodec->rtpLength() : 1; - int frameLength = mCodec->rtpLength() ? mCodec->rtpLength() : (int)p->GetPayloadLength(); - - // Save last packet time length - mLastPacketTimeLength = mFrameCount * mCodec->frameTime(); - - // Decode - for (int i=0; idecode(p->GetPayloadData() + i*mCodec->rtpLength(), - frameLength, mDecodedFrame, sizeof mDecodedFrame); - if (mDecodedLength) - processDecoded(output, options); - } + mCngPacket = p->rtp(); + mCngDecoder.decode3389(p->rtp()->GetPayloadData(), p->rtp()->GetPayloadLength()); + // Emit CNG mLastPacketLength milliseconds + mDecodedLength = mCngDecoder.produce(mCodec->samplerate(), mLastPacketTimeLength, + (short*)mDecodedFrame, true); + if (mDecodedLength) + processDecoded(output, options); } - result = mFrameCount > 0; - - // Check for bitrate counter - processStatisticsWithAmrCodec(mCodec.get()); + result = true; } else - ICELogMedia(<< "RTP packet with tail."); + { + // Reset CNG packet + mCngPacket.reset(); + + // Handle here regular RTP packets + // Check if payload length is ok + int tail = mCodec->rtpLength() ? p->rtp()->GetPayloadLength() % mCodec->rtpLength() : 0; + + if (!tail) + { + // Find number of frames + mFrameCount = mCodec->rtpLength() ? p->rtp()->GetPayloadLength() / mCodec->rtpLength() : 1; + int frameLength = mCodec->rtpLength() ? mCodec->rtpLength() : (int)p->rtp()->GetPayloadLength(); + + // Save last packet time length + mLastPacketTimeLength = mFrameCount * mCodec->frameTime(); + + // Decode + for (int i=0; idecode(p->rtp()->GetPayloadData() + i * mCodec->rtpLength(), + frameLength, mDecodedFrame, sizeof mDecodedFrame); + // mDecodedLength = 3840; // Opus 20 ms stereo + if (mDecodedLength) + processDecoded(output, options); + } + } + result = mFrameCount > 0; + + // Check for bitrate counter + processStatisticsWithAmrCodec(mCodec.get()); + } + else + ICELogMedia(<< "RTP packet with tail."); + + } } } } @@ -594,6 +659,18 @@ bool AudioReceiver::getAudio(Audio::DataWindow& output, int options, int* rate) assert(0); } + if (had_decode) + { + // mStat.mDecodeRequested++; + if (mLastDecodeTime == 0.0) + mLastDecodeTime = now_ms(); + else + { + float t = now_ms(); + mStat.mDecodingInterval.process(t - mLastDecodeTime); + mLastDecodeTime = t; + } + } return result; } @@ -670,12 +747,12 @@ void AudioReceiver::updatePvqa(const void *data, int size) mPvqaBuffer->addZero(size); Audio::Format fmt; - int frames = (int)fmt.timeFromSize(mPvqaBuffer->filled()) / (PVQA_INTERVAL * 1000); + int frames = static_cast(fmt.timeFromSize(mPvqaBuffer->filled())) / (PVQA_INTERVAL * 1000); if (frames > 0) { int time4pvqa = (int)(frames * PVQA_INTERVAL * 1000); int size4pvqa = (int)fmt.sizeFromTime(time4pvqa); - ICELogInfo(<< "PVQA buffer has " << time4pvqa << " milliseconds of audio."); + ICELogDebug(<< "PVQA buffer has " << time4pvqa << " milliseconds of audio."); mPVQA->update(mPvqaBuffer->data(), size4pvqa); mPvqaBuffer->erase(size4pvqa); } diff --git a/src/engine/media/MT_AudioReceiver.h b/src/engine/media/MT_AudioReceiver.h index 1f1cece3..b1f9f7e3 100644 --- a/src/engine/media/MT_AudioReceiver.h +++ b/src/engine/media/MT_AudioReceiver.h @@ -47,12 +47,17 @@ namespace MT public: Packet(const std::shared_ptr& packet, int timelen, int rate); std::shared_ptr rtp() const; + int timelength() const; int rate() const; + const std::vector& pcm() const; + std::vector& pcm(); + protected: std::shared_ptr mRtp; int mTimelength = 0, mRate = 0; + std::vector mPcm; }; RtpBuffer(Statistics& stat); @@ -60,35 +65,48 @@ namespace MT unsigned ssrc(); void setSsrc(unsigned ssrc); + void setHigh(int milliseconds); int high(); + void setLow(int milliseconds); int low(); + void setPrebuffer(int milliseconds); int prebuffer(); + int getNumberOfReturnedPackets() const; int getNumberOfAddPackets() const; + int findTimelength(); int getCount() const; - // Returns false if packet was not add - maybe too old or too new or duplicate - bool add(std::shared_ptr packet, int timelength, int rate); - typedef std::vector> ResultList; + // Returns false if packet was not add - maybe too old or too new or duplicate + std::shared_ptr add(std::shared_ptr packet, int timelength, int rate); + + typedef std::vector> ResultList; typedef std::shared_ptr PResultList; FetchResult fetch(ResultList& rl); protected: - unsigned mSsrc; - int mHigh, mLow, mPrebuffer; - int mReturnedCounter, mAddCounter; + unsigned mSsrc = 0; + int mHigh = RTP_BUFFER_HIGH, + mLow = RTP_BUFFER_LOW, + mPrebuffer = RTP_BUFFER_PREBUFFER; + int mReturnedCounter = 0, + mAddCounter = 0; + mutable Mutex mGuard; - typedef std::vector PacketList; + typedef std::vector> PacketList; PacketList mPacketList; Statistics& mStat; - bool mFirstPacketWillGo; + bool mFirstPacketWillGo = true; jrtplib::RTPSourceStats mRtpStats; - Packet mFetchedPacket; + std::shared_ptr mFetchedPacket; + + // To calculate average interval between packet add. It is close to jitter but more useful in debugging. + float mLastAddTime = 0.0; }; class Receiver @@ -146,6 +164,9 @@ namespace MT std::shared_ptr mCngPacket; CngDecoder mCngDecoder; + // Decode RTP early, do not wait for speaker callback + bool mEarlyDecode = false; + // Buffer to hold decoded data char mDecodedFrame[65536]; int mDecodedLength = 0; @@ -161,12 +182,17 @@ namespace MT // Last packet time length int mLastPacketTimeLength = 0; - int mFailedCount; + int mFailedCount = 0; Audio::Resampler mResampler8, mResampler16, mResampler32, mResampler48; Audio::PWavFileWriter mDecodedDump; + float mLastDecodeTime = 0.0; // Time last call happened to codec->decode() + + float mIntervalSum = 0.0; + int mIntervalCount = 0; + // Zero rate will make audio mono but resampling will be skipped void makeMonoAndResample(int rate, int channels); diff --git a/src/engine/media/MT_AudioStream.cpp b/src/engine/media/MT_AudioStream.cpp index 3f14e31a..32fa0d9d 100644 --- a/src/engine/media/MT_AudioStream.cpp +++ b/src/engine/media/MT_AudioStream.cpp @@ -97,7 +97,7 @@ AudioStream::~AudioStream() if (mFinalStatistics) *mFinalStatistics = mStat; - ICELogInfo(<< mStat.toShortString()); + ICELogInfo(<< mStat.toString()); } void AudioStream::setDestination(const RtpPair& dest) diff --git a/src/engine/media/MT_Box.cpp b/src/engine/media/MT_Box.cpp index 6c0a2df5..39609402 100644 --- a/src/engine/media/MT_Box.cpp +++ b/src/engine/media/MT_Box.cpp @@ -34,7 +34,7 @@ Terminal::~Terminal() mAudioPair.reset(); } -PStream Terminal::createStream(int type, VariantMap& config) +PStream Terminal::createStream(int type, VariantMap& /*config*/) { PStream result; switch (type) @@ -52,7 +52,7 @@ PStream Terminal::createStream(int type, VariantMap& config) return result; } -void Terminal::freeStream(PStream stream) +void Terminal::freeStream(const PStream& stream) { if (AudioStream* audio = dynamic_cast(stream.get())) { diff --git a/src/engine/media/MT_Box.h b/src/engine/media/MT_Box.h index 72f5afa1..b4c33e54 100644 --- a/src/engine/media/MT_Box.h +++ b/src/engine/media/MT_Box.h @@ -26,10 +26,11 @@ namespace MT CodecList& codeclist(); PStream createStream(int type, VariantMap& config); - void freeStream(PStream s); + void freeStream(const PStream& s); Audio::PDevicePair audio(); void setAudio(const Audio::PDevicePair& audio); + protected: StreamList mAudioList; std::mutex mAudioListMutex; diff --git a/src/engine/media/MT_Codec.h b/src/engine/media/MT_Codec.h index 97d2d41f..8ddcf576 100644 --- a/src/engine/media/MT_Codec.h +++ b/src/engine/media/MT_Codec.h @@ -63,8 +63,13 @@ public: virtual int channels() { return 1; } + // Returns size of encoded data (RTP) in bytes virtual int encode(const void* input, int inputBytes, void* output, int outputCapacity) = 0; + + // Returns size of decoded data (PCM signed short) in bytes virtual int decode(const void* input, int inputBytes, void* output, int outputCapacity) = 0; + + // Returns size of produced data (PCM signed short) in bytes virtual int plc(int lostFrames, void* output, int outputCapacity) = 0; // Returns size of codec in memory diff --git a/src/engine/media/MT_CodecList.cpp b/src/engine/media/MT_CodecList.cpp index e9adcc70..1d7ea616 100644 --- a/src/engine/media/MT_CodecList.cpp +++ b/src/engine/media/MT_CodecList.cpp @@ -25,6 +25,42 @@ using namespace MT; using strx = StringHelper; // ---------------- EvsSpec --------------- + +std::string CodecList::Settings::toString() const +{ + std::ostringstream oss; + oss << "wrap IuUP: " << mWrapIuUP << std::endl + << "skip decode: " << mSkipDecode << std::endl; + for (int ptype: mAmrWbPayloadType) + oss << "AMR WB ptype: " << ptype << std::endl; + for (int ptype: mAmrWbOctetPayloadType) + oss << "AMR WB octet-aligned ptype: " << ptype << std::endl; + for (int ptype: mAmrNbPayloadType) + oss << "AMR NB ptype: " << ptype << std::endl; + for (int ptype: mAmrNbOctetPayloadType) + oss << "AMR NB octet-aligned ptype:" << ptype << std::endl; + + oss << "ISAC 16Khz ptype: " << mIsac16KPayloadType << std::endl + << "ISAC 32Khz ptype: " << mIsac32KPayloadType << std::endl + << "iLBC 20ms ptype: " << mIlbc20PayloadType << std::endl + << "iLBC 30ms ptype: " << mIlbc30PayloadType << std::endl + << "GSM FR ptype: " << mGsmFrPayloadType << ", GSM FR plength: " << mGsmFrPayloadLength << std::endl + << "GSM HR ptype: " << mGsmHrPayloadType << std::endl + << "GSM EFR ptype: " << mGsmEfrPayloadType << std::endl; + + for (auto& spec: mEvsSpec) + { + oss << "EVS ptype: " << spec.mPayloadType << ", bw: " << spec.mBandwidth << ", enc: " << (spec.mEncodingType == EvsSpec::Encoding_MIME ? "mime" : "g192") << std::endl; + } + + for (auto& spec: mOpusSpec) + { + oss << "OPUS ptype: " << spec.mPayloadType << ", rate: " << spec.mRate << ", channels: " << spec.mChannels << std::endl; + } + + return oss.str(); +} + bool CodecList::Settings::EvsSpec::isValid() const { return mPayloadType >= 96 && mPayloadType <= 127; @@ -125,7 +161,6 @@ CodecList::CodecList(const Settings& settings) mFactoryList.push_back(new GsmEfrCodec::GsmEfrFactory(mSettings.mWrapIuUP, mSettings.mGsmEfrPayloadType)); #endif #endif - mFactoryList.push_back(new IsacCodec::IsacFactory16K(mSettings.mIsac16KPayloadType)); mFactoryList.push_back(new IlbcCodec::IlbcFactory(mSettings.mIlbc20PayloadType, mSettings.mIlbc30PayloadType)); mFactoryList.push_back(new G711Codec::AlawFactory()); diff --git a/src/engine/media/MT_CodecList.h b/src/engine/media/MT_CodecList.h index 1024dc26..ef84ba47 100644 --- a/src/engine/media/MT_CodecList.h +++ b/src/engine/media/MT_CodecList.h @@ -82,6 +82,9 @@ public: }; std::vector mOpusSpec; + // Textual representation - used in logging + std::string toString() const; + static Settings DefaultSettings; }; diff --git a/src/engine/media/MT_SingleAudioStream.cpp b/src/engine/media/MT_SingleAudioStream.cpp index 4b4273b0..6754b8cf 100644 --- a/src/engine/media/MT_SingleAudioStream.cpp +++ b/src/engine/media/MT_SingleAudioStream.cpp @@ -19,6 +19,7 @@ SingleAudioStream::SingleAudioStream(const CodecList::Settings& codecSettings, S SingleAudioStream::~SingleAudioStream() { + } void SingleAudioStream::process(const std::shared_ptr& packet) diff --git a/src/engine/media/MT_Statistics.cpp b/src/engine/media/MT_Statistics.cpp index aa0c3c9d..acad1e07 100644 --- a/src/engine/media/MT_Statistics.cpp +++ b/src/engine/media/MT_Statistics.cpp @@ -191,6 +191,9 @@ Statistics& Statistics::operator += (const Statistics& src) mJitter = src.mJitter; mRttDelay = src.mRttDelay; + mDecodingInterval = src.mDecodingInterval; + mDecodeRequested = src.mDecodeRequested; + if (!src.mCodecName.empty()) mCodecName = src.mCodecName; @@ -239,13 +242,16 @@ Statistics& Statistics::operator -= (const Statistics& src) } -std::string Statistics::toShortString() const +std::string Statistics::toString() const { std::ostringstream oss; oss << "Received: " << mReceivedRtp << ", lost: " << mPacketLoss << ", dropped: " << mPacketDropped - << ", sent: " << mSentRtp; + << ", sent: " << mSentRtp + << ", decoding interval: " << mDecodingInterval.average() + << ", decode requested: " << mDecodeRequested.average() + << ", packet interval: " << mPacketInterval.average(); return oss.str(); } diff --git a/src/engine/media/MT_Statistics.h b/src/engine/media/MT_Statistics.h index e7d10f08..b5c054ac 100644 --- a/src/engine/media/MT_Statistics.h +++ b/src/engine/media/MT_Statistics.h @@ -6,6 +6,8 @@ #include "audio/Audio_DataWindow.h" #include "helper/HL_Optional.hpp" +#include "helper/HL_Statistics.h" + #include "jrtplib/src/rtptimeutilities.h" #include "jrtplib/src/rtppacket.h" @@ -13,78 +15,6 @@ using std::experimental::optional; namespace MT { -template -struct Average -{ - int mCount = 0; - T mSum = 0; - T average() const - { - if (!mCount) - return 0; - return mSum / mCount; - } - - T value() const - { - return average(); - } - - void process(T value) - { - mCount++; - mSum += value; - } -}; - -template -struct TestResult -{ - T mMin = minimum; - T mMax = maximum; - Average mAverage; - T mCurrent = default_value; - - void process(T value) - { - if (mMin > value) - mMin = value; - if (mMax < value) - mMax = value; - mCurrent = value; - mAverage.process(value); - } - - bool is_initialized() const - { - return mAverage.mCount > 0; - } - - T current() const - { - if (is_initialized()) - return mCurrent; - else - return 0; - } - - T value() const - { - return current(); - } - - TestResult& operator = (T value) - { - process(value); - return *this; - } - - operator T() - { - return mCurrent; - } -}; - template struct StreamStats @@ -130,9 +60,13 @@ public: mDuplicatedRtp, // Number of received duplicated rtp packets mOldRtp, // Number of late rtp packets mPacketLoss, // Number of lost packets - mPacketDropped, // Number of dropped packets (due to time unsync when playing) + mPacketDropped, // Number of dropped packets (due to time unsync when playing)б mIllegalRtp; // Number of rtp packets with bad payload type + 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 + int mLoss[128]; // Every item is number of loss of corresping length size_t mAudioTime; // Decoded/found time in milliseconds uint16_t mSsrc; // Last known SSRC ID in a RTP stream @@ -169,7 +103,7 @@ public: std::string mPvqaReport; #endif - std::string toShortString() const; + std::string toString() const; }; } // end of namespace MT diff --git a/src/engine/media/MT_Stream.cpp b/src/engine/media/MT_Stream.cpp index 68fee660..c09fbcb3 100644 --- a/src/engine/media/MT_Stream.cpp +++ b/src/engine/media/MT_Stream.cpp @@ -21,7 +21,7 @@ Stream::Stream() Stream::~Stream() { - + ICELogInfo(<< mStat.toString()); } void Stream::setDestination(const RtpPair& dest) @@ -77,13 +77,13 @@ StreamList::~StreamList() clear(); } -void StreamList::add(PStream s) +void StreamList::add(const PStream& s) { Lock l(mMutex); mStreamVector.push_back(s); } -void StreamList::remove(PStream s) +void StreamList::remove(const PStream& s) { Lock l(mMutex); @@ -98,7 +98,7 @@ void StreamList::clear() mStreamVector.clear(); } -bool StreamList::has(PStream s) +bool StreamList::has(const PStream& s) { Lock l(mMutex); return std::find(mStreamVector.begin(), mStreamVector.end(), s) != mStreamVector.end(); @@ -127,4 +127,4 @@ void StreamList::copyTo(StreamList* sl) Mutex& StreamList::getMutex() { return mMutex; -} \ No newline at end of file +} diff --git a/src/engine/media/MT_Stream.h b/src/engine/media/MT_Stream.h index e517de9f..9be3f9ea 100644 --- a/src/engine/media/MT_Stream.h +++ b/src/engine/media/MT_Stream.h @@ -88,10 +88,10 @@ namespace MT StreamList(); ~StreamList(); - void add(PStream s); - void remove(PStream s); + void add(const PStream& s); + void remove(const PStream& s); void clear(); - bool has(PStream s); + bool has(const PStream& s); int size(); PStream streamAt(int index); diff --git a/src/libs/ice/ICEAddress.cpp b/src/libs/ice/ICEAddress.cpp index f8633f69..e27d837b 100644 --- a/src/libs/ice/ICEAddress.cpp +++ b/src/libs/ice/ICEAddress.cpp @@ -24,7 +24,7 @@ # include #else # include - + # if /*defined(TARGET_LINUX) || */ defined(TARGET_ANDROID) # include # endif @@ -465,15 +465,15 @@ unsigned char* NetworkAddress::ipBytes() const #endif case AF_INET6: #ifdef TARGET_WIN - return (unsigned char*)mAddr6.sin6_addr.u.Byte; -#elif defined(TARGET_OSX) || defined(TARGET_IOS) - return (unsigned char*)&mAddr6.sin6_addr.__u6_addr.__u6_addr8; -#elif defined(TARGET_OPENWRT) || defined(TARGET_MUSL) - return (unsigned char*)&mAddr6.sin6_addr.__in6_union.__s6_addr; -#elif defined(TARGET_LINUX) - return (unsigned char*)&mAddr6.sin6_addr.__in6_u.__u6_addr8; -#elif defined(TARGET_ANDROID) - return (unsigned char*)&mAddr6.sin6_addr.in6_u.u6_addr8; + return (unsigned char*)mAddr6.sin6_addr.u.Byte; +#elif defined(TARGET_OSX) || defined(TARGET_IOS) + return (unsigned char*)&mAddr6.sin6_addr.__u6_addr.__u6_addr8; +#elif defined(TARGET_OPENWRT) || defined(TARGET_MUSL) + return (unsigned char*)&mAddr6.sin6_addr.__in6_union.__s6_addr; +#elif defined(TARGET_LINUX) + return (unsigned char*)&mAddr6.sin6_addr.__in6_u.__u6_addr8; +#elif defined(TARGET_ANDROID) + return (unsigned char*)&mAddr6.sin6_addr.in6_u.u6_addr8; #endif } assert(0); @@ -783,3 +783,12 @@ bool NetworkAddress::isSame(const NetworkAddress& a1, const NetworkAddress& a2) } return false; } + +NetworkAddress& NetworkAddress::operator = (const NetworkAddress& src) +{ + this->mInitialized = src.mInitialized; + this->mRelayed = src.mRelayed; + this->mAddr6 = src.mAddr6; + + return *this; +} diff --git a/src/libs/ice/ICEAddress.h b/src/libs/ice/ICEAddress.h index 6d8e346c..2bd524de 100644 --- a/src/libs/ice/ICEAddress.h +++ b/src/libs/ice/ICEAddress.h @@ -75,7 +75,9 @@ namespace ice static bool isSameHost(const NetworkAddress& a1, const NetworkAddress& a2); static bool isSame(const NetworkAddress& a1, const NetworkAddress& a2); - + + NetworkAddress& operator = (const NetworkAddress& src); + bool operator == (const NetworkAddress& rhs) const; bool operator != (const NetworkAddress& rhs) const; bool operator < (const NetworkAddress& rhs) const; diff --git a/src/libs/ice/ICELog.h b/src/libs/ice/ICELog.h index 6e120fa4..105213a7 100644 --- a/src/libs/ice/ICELog.h +++ b/src/libs/ice/ICELog.h @@ -150,7 +150,7 @@ extern Logger GLogger; {\ if (GLogger.level() >= level_)\ {\ - LogLock l(GLogger.mutex());\ + LogLock log_lock(GLogger.mutex());\ GLogger.beginLine(level_, __FILE__, __LINE__, subsystem_);\ GLogger args_;\ GLogger.endLine();\ diff --git a/src/libs/jrtplib/src/rtptimeutilities.h b/src/libs/jrtplib/src/rtptimeutilities.h index 4330f713..467a558c 100644 --- a/src/libs/jrtplib/src/rtptimeutilities.h +++ b/src/libs/jrtplib/src/rtptimeutilities.h @@ -69,6 +69,8 @@ public: /** Returns the least significant word. */ uint32_t GetLSW() const { return lsw; } + + uint64_t Get64() const { return (uint64_t)msw << 32 | lsw; } private: uint32_t msw,lsw; }; diff --git a/src/libs/oboe/.gitignore b/src/libs/oboe/.gitignore new file mode 100644 index 00000000..76e7cb3c --- /dev/null +++ b/src/libs/oboe/.gitignore @@ -0,0 +1,8 @@ +*/.DS_Store +.DS_Store +.externalNativeBuild/ +.cxx/ +.idea +build +.logpile + diff --git a/src/libs/oboe/AUTHORS b/src/libs/oboe/AUTHORS new file mode 100644 index 00000000..6ca313d2 --- /dev/null +++ b/src/libs/oboe/AUTHORS @@ -0,0 +1,9 @@ +# This is the official list of authors for copyright purposes. +# This file is distinct from the CONTRIBUTORS files. +# See the latter for an explanation. + +# Names should be added to this file as: +# Name or Organization +# The email address is not required for organizations. + +Google Inc. diff --git a/src/libs/oboe/CMakeLists.txt b/src/libs/oboe/CMakeLists.txt new file mode 100644 index 00000000..211e6b78 --- /dev/null +++ b/src/libs/oboe/CMakeLists.txt @@ -0,0 +1,98 @@ +cmake_minimum_required(VERSION 3.4.1) + +# Set the name of the project and store it in PROJECT_NAME. Also set the following variables: +# PROJECT_SOURCE_DIR (usually the root directory where Oboe has been cloned e.g.) +# PROJECT_BINARY_DIR (usually the containing project's binary directory, +# e.g. ${OBOE_HOME}/samples/RhythmGame/.externalNativeBuild/cmake/ndkExtractorDebug/x86/oboe-bin) +project(oboe) + +set (oboe_sources + src/aaudio/AAudioLoader.cpp + src/aaudio/AudioStreamAAudio.cpp + src/common/AudioSourceCaller.cpp + src/common/AudioStream.cpp + src/common/AudioStreamBuilder.cpp + src/common/DataConversionFlowGraph.cpp + src/common/FilterAudioStream.cpp + src/common/FixedBlockAdapter.cpp + src/common/FixedBlockReader.cpp + src/common/FixedBlockWriter.cpp + src/common/LatencyTuner.cpp + src/common/SourceFloatCaller.cpp + src/common/SourceI16Caller.cpp + src/common/SourceI24Caller.cpp + src/common/SourceI32Caller.cpp + src/common/Utilities.cpp + src/common/QuirksManager.cpp + src/fifo/FifoBuffer.cpp + src/fifo/FifoController.cpp + src/fifo/FifoControllerBase.cpp + src/fifo/FifoControllerIndirect.cpp + src/flowgraph/FlowGraphNode.cpp + src/flowgraph/ChannelCountConverter.cpp + src/flowgraph/ClipToRange.cpp + src/flowgraph/ManyToMultiConverter.cpp + src/flowgraph/MonoToMultiConverter.cpp + src/flowgraph/MultiToMonoConverter.cpp + src/flowgraph/RampLinear.cpp + src/flowgraph/SampleRateConverter.cpp + src/flowgraph/SinkFloat.cpp + src/flowgraph/SinkI16.cpp + src/flowgraph/SinkI24.cpp + src/flowgraph/SinkI32.cpp + src/flowgraph/SourceFloat.cpp + src/flowgraph/SourceI16.cpp + src/flowgraph/SourceI24.cpp + src/flowgraph/SourceI32.cpp + src/flowgraph/resampler/IntegerRatio.cpp + src/flowgraph/resampler/LinearResampler.cpp + src/flowgraph/resampler/MultiChannelResampler.cpp + src/flowgraph/resampler/PolyphaseResampler.cpp + src/flowgraph/resampler/PolyphaseResamplerMono.cpp + src/flowgraph/resampler/PolyphaseResamplerStereo.cpp + src/flowgraph/resampler/SincResampler.cpp + src/flowgraph/resampler/SincResamplerStereo.cpp + src/opensles/AudioInputStreamOpenSLES.cpp + src/opensles/AudioOutputStreamOpenSLES.cpp + src/opensles/AudioStreamBuffered.cpp + src/opensles/AudioStreamOpenSLES.cpp + src/opensles/EngineOpenSLES.cpp + src/opensles/OpenSLESUtilities.cpp + src/opensles/OutputMixerOpenSLES.cpp + src/common/StabilizedCallback.cpp + src/common/Trace.cpp + src/common/Version.cpp + ) + +add_library(oboe ${oboe_sources}) + +# Specify directories which the compiler should look for headers +target_include_directories(oboe + PRIVATE src + PUBLIC include) + +# Compile Flags: +# Enable -Werror when building debug config +# Enable -Ofast +target_compile_options(oboe + PRIVATE + -std=c++17 + -Wall + -Wextra-semi + -Wshadow + -Wshadow-field + -Ofast + "$<$:-Werror>") + +# Enable logging of D,V for debug builds +target_compile_definitions(oboe PUBLIC $<$:OBOE_ENABLE_LOGGING=1>) + +target_link_libraries(oboe PRIVATE log OpenSLES) + +# When installing oboe put the libraries in the lib/ folder e.g. lib/arm64-v8a +install(TARGETS oboe + LIBRARY DESTINATION lib/${ANDROID_ABI} + ARCHIVE DESTINATION lib/${ANDROID_ABI}) + +# Also install the headers +install(DIRECTORY include/oboe DESTINATION include) \ No newline at end of file diff --git a/src/libs/oboe/CONTRIBUTING b/src/libs/oboe/CONTRIBUTING new file mode 100644 index 00000000..5d9ff17d --- /dev/null +++ b/src/libs/oboe/CONTRIBUTING @@ -0,0 +1 @@ +Please see the CONTRIBUTING.md file for more information. diff --git a/src/libs/oboe/CONTRIBUTING.md b/src/libs/oboe/CONTRIBUTING.md new file mode 100644 index 00000000..88f06d06 --- /dev/null +++ b/src/libs/oboe/CONTRIBUTING.md @@ -0,0 +1,25 @@ +Want to contribute? Great! First, read this page (including the small print at the end). + +### Before you contribute +Before we can use your code, you must sign the +[Google Individual Contributor License +Agreement](https://developers.google.com/open-source/cla/individual?csw=1) +(CLA), which you can do online. The CLA is necessary mainly because you own the +copyright to your changes, even after your contribution becomes part of our +codebase, so we need your permission to use and distribute your code. We also +need to be sure of various other things—for instance that you'll tell us if you +know that your code infringes on other people's patents. You don't have to sign +the CLA until after you've submitted your code for review and a member has +approved it, but you must do it before we can put your code into our codebase. +Before you start working on a larger contribution, you should get in touch with +us first through the issue tracker with your idea so that we can help out and +possibly guide you. Coordinating up front makes it much easier to avoid +frustration later on. + +### Code reviews +All submissions, including submissions by project members, require review. We +use Github pull requests for this purpose. + +### The small print +Contributions made by corporations are covered by a different agreement than +the one above, the Software Grant and Corporate Contributor License Agreement. diff --git a/src/libs/oboe/CONTRIBUTORS b/src/libs/oboe/CONTRIBUTORS new file mode 100644 index 00000000..0b77924b --- /dev/null +++ b/src/libs/oboe/CONTRIBUTORS @@ -0,0 +1,14 @@ +# People who have agreed to one of the CLAs and can contribute patches. +# The AUTHORS file lists the copyright holders; this file +# lists people. For example, Google employees are listed here +# but not in AUTHORS, because Google holds the copyright. +# +# https://developers.google.com/open-source/cla/individual +# https://developers.google.com/open-source/cla/corporate +# +# Names should be added to this file as: +# Name + +Phil Burk +Don Turner +Mikhail Naganov diff --git a/src/libs/oboe/Doxyfile b/src/libs/oboe/Doxyfile new file mode 100644 index 00000000..f0e0be32 --- /dev/null +++ b/src/libs/oboe/Doxyfile @@ -0,0 +1,2482 @@ +# Doxyfile 1.8.14 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all text +# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv +# built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "Oboe" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = 1.5 + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = "A library for creating real-time audio apps on Android" + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = docs + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines (in the resulting output). You can put ^^ in the value part of an +# alias to insert a newline as if a physical newline was in the original file. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: +# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: +# Fortran. In the later case the parser tries to guess whether the code is fixed +# or free formatted code, this is the default for Fortran type files), VHDL. For +# instance to make doxygen treat .inc files as Fortran files (default is PHP), +# and .f files as C (default is Fortran), use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 0. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 0 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO, these declarations will be +# included in the documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES, upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = include + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: https://www.gnu.org/software/libiconv/) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, +# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f \ + *.for \ + *.tcl \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# function all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = reference + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via Javascript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have Javascript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: https://developer.apple.com/tools/xcode/), introduced with +# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the master .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://doc.qt.io/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://doc.qt.io/qt-4.8/qthelpproject.html#virtual-folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://doc.qt.io/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://doc.qt.io/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://doc.qt.io/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANSPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. +# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/ + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /