- removed old Sevana routines (they are moved to pvqa++ / aqua++ header files)

- cleanups
This commit is contained in:
Dmytro Bogovych 2019-06-22 12:00:49 +03:00
parent 477932459f
commit 682362c6fe
11 changed files with 875 additions and 2117 deletions

View File

@ -91,7 +91,6 @@ set (RTPHONE_SOURCES
${rtphone_engine}/media/MT_AudioStream.cpp
${rtphone_engine}/media/MT_AudioReceiver.cpp
${rtphone_engine}/media/MT_AudioCodec.cpp
${rtphone_engine}/media/MT_SevanaMos.cpp
${rtphone_engine}/media/MT_AmrCodec.cpp
${rtphone_engine}/media/MT_EvsCodec.cpp
${rtphone_engine}/media/MT_CngHelper.cpp
@ -120,7 +119,6 @@ set (RTPHONE_HEADERS
${rtphone_engine}/media/MT_AudioStream.h
${rtphone_engine}/media/MT_AudioReceiver.h
${rtphone_engine}/media/MT_AudioCodec.h
${rtphone_engine}/media/MT_SevanaMos.h
${rtphone_engine}/media/MT_AmrCodec.h
${rtphone_engine}/media/MT_EvsCodec.h
${rtphone_engine}/media/MT_CngHelper.h

View File

@ -4,12 +4,16 @@
#include "helper/HL_StreamState.h"
#include "helper/HL_VariantMap.h"
#include "helper/HL_CsvReader.h"
#if defined(TARGET_ANDROID)
//# include "CAndroidSystemInfo.h"
#endif
#include <fstream>
#if defined(USE_PVQA_LIBRARY)
# include "pvqa++.h"
#endif
#if defined(USE_AQUA_LIBRARY)
# include "aqua++.h"
#endif
const std::string Status_Ok = "ok";
const std::string Status_SessionNotFound = "session not found";
const std::string Status_AccountNotFound = "account not found";
@ -18,7 +22,6 @@ const std::string Status_NoActiveProvider = "no active provider";
const std::string Status_NoMediaAction = "no valid media action";
const std::string Status_NoCommand = "no valid command";
#define LOG_SUBSYSTEM "Agent"
AgentImpl::AgentImpl()
@ -135,7 +138,7 @@ std::string AgentImpl::command(const std::string& command)
return answer.toStyledString();
}
bool AgentImpl::waitForData(int milliseconds)
bool AgentImpl::waitForData(int /*milliseconds*/)
{
return false;
}
@ -154,13 +157,13 @@ void AgentImpl::processConfig(Json::Value &d, Json::Value &answer)
// Because Android requires special initializing procedure (valid JNI environment context)
std::string pvqaLicense = d["pvqa-license"].asString(), pvqaConfig = d["pvqa-config"].asString();
if (!pvqaLicense.empty() && !pvqaConfig.empty())
MT::SevanaPVQA::initializeLibrary(pvqaLicense, pvqaConfig);
sevana::pvqa::initialize(pvqaLicense, pvqaConfig);
#endif
#if !defined(TARGET_ANDROID) && defined(USE_AQUA_LIBRARY)
std::string aquaLicense = d["aqua-license"].asString();
if (!aquaLicense.empty())
MT::SevanaAqua::initializeLibrary(aquaLicense);
sevana::aqua::initializeLibrary(aquaLicense);
#endif
std::string transport = d["transport"].asString();
@ -182,7 +185,7 @@ void AgentImpl::processConfig(Json::Value &d, Json::Value &answer)
answer["status"] = Status_Ok;
}
void AgentImpl::processStart(Json::Value &request, Json::Value &answer)
void AgentImpl::processStart(Json::Value& /*request*/, Json::Value &answer)
{
std::unique_lock<std::recursive_mutex> l(mAgentMutex);
if (mThread)
@ -224,7 +227,7 @@ void AgentImpl::processStart(Json::Value &request, Json::Value &answer)
answer["status"] = Status_Ok;
}
void AgentImpl::processStop(Json::Value& request, Json::Value& answer)
void AgentImpl::processStop(Json::Value& /*request*/, Json::Value& answer)
{
stopAgentAndThread();
answer["status"] = Status_Ok;
@ -326,7 +329,7 @@ void AgentImpl::processStartSession(Json::Value& request, Json::Value& answer)
// Ensure audio provider is here
PSession session = sessionIter->second;
PDataProvider audioProvider = std::make_shared<AudioProvider>(*this, *mTerminal);
audioProvider->setState(audioProvider->state() | (int)StreamState::Grabbing | (int)StreamState::Playing);
audioProvider->setState(audioProvider->state() | static_cast<int>(StreamState::Grabbing) | static_cast<int>(StreamState::Playing));
#if defined(USE_AQUA_LIBRARY)
std::string temp_path = request["aqua_temp_path"].asString();
@ -453,8 +456,8 @@ static Json::Value CsvReportToJson(const std::string& report)
if (reader.readLine(cells))
{
Json::Value detectorNames;
for (int nameIndex = 0; nameIndex < (int)cells.size(); nameIndex++)
detectorNames[nameIndex] = StringHelper::trim(cells[nameIndex]);
for (size_t nameIndex = 0; nameIndex < cells.size(); nameIndex++)
detectorNames[static_cast<int>(nameIndex)] = StringHelper::trim(cells[nameIndex]);
// Put first line name of columns
detectorValues[0] = detectorNames;
@ -463,14 +466,14 @@ static Json::Value CsvReportToJson(const std::string& report)
{
// Skip last column for now
Json::Value row;
for (int valueIndex = 0; valueIndex < (int)cells.size(); valueIndex++)
for (size_t valueIndex = 0; valueIndex < cells.size(); valueIndex++)
{
bool isFloat = true;
float v = StringHelper::toFloat(cells[valueIndex], 0.0, &isFloat);
if (isFloat)
row[valueIndex] = v;
row[static_cast<int>(valueIndex)] = static_cast<double>(v);
else
row[valueIndex] = cells[valueIndex];
row[static_cast<int>(valueIndex)] = cells[valueIndex];
}
detectorValues[rowIndex++] = row;
}
@ -575,7 +578,7 @@ void AgentImpl::processGetMediaStats(Json::Value& request, Json::Value& answer)
answer["status"] = Status_SessionNotFound;
}
void AgentImpl::processNetworkChanged(Json::Value& request, Json::Value& answer)
void AgentImpl::processNetworkChanged(Json::Value& /*request*/, Json::Value& /*answer*/)
{
std::unique_lock<std::recursive_mutex> l(mAgentMutex);
}
@ -596,7 +599,7 @@ void AgentImpl::processAddRootCert(Json::Value& request, Json::Value& answer)
{
// Get single certificate
std::string cert = pem.substr(pb, pe + EndCertificate.size());
int size = cert.size();
//int size = cert.size();
addRootCert(ByteBuffer(cert.c_str(), cert.size()));
// Delete processed part
@ -611,7 +614,7 @@ void AgentImpl::processLogMessage(Json::Value &request, Json::Value &answer)
int level = request["level"].asInt();
std::string message = request["message"].asString();
ICELog((ice::LogLevel)level, "App", << message);
ICELog(static_cast<ice::LogLevel>(level), "App", << message);
answer["status"] = Status_Ok;
}
@ -889,7 +892,7 @@ void AgentImpl::onCheckFinished(PSession s, const char* description)
}
// Called when log message must be recorded
void AgentImpl::onLog(const char* msg)
void AgentImpl::onLog(const char* /*msg*/)
{}
// Called when problem with SIP connection(s) detected

View File

@ -1,4 +1,4 @@
/* Copyright(C) 2007-2014 VoIP objects (voipobjects.com)
/* Copyright(C) 2007-2019 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/. */
@ -29,6 +29,10 @@
#endif
// ----------------------------- SocketSink -------------------------
SocketSink::~SocketSink()
{}
// ----------------------------- SocketHeap -------------------------
SocketHeap::SocketHeap(unsigned short start, unsigned short finish)
@ -132,7 +136,7 @@ PDatagramSocket SocketHeap::allocSocket(int family, SocketSink* sink, int port)
// Obtain port number
sockaddr_in addr;
sockaddr_in6 addr6;
int result;
int result = 0;
int testport;
do
{
@ -144,7 +148,7 @@ PDatagramSocket SocketHeap::allocSocket(int family, SocketSink* sink, int port)
memset(&addr, 0, sizeof addr);
addr.sin_family = AF_INET;
addr.sin_port = htons(testport);
result = ::bind(sock, (const sockaddr*)&addr, sizeof addr);
result = ::bind(sock, reinterpret_cast<const sockaddr*>(&addr), sizeof addr);
if (result)
result = WSAGetLastError();
break;
@ -153,7 +157,7 @@ PDatagramSocket SocketHeap::allocSocket(int family, SocketSink* sink, int port)
memset(&addr6, 0, sizeof addr6);
addr6.sin6_family = AF_INET6;
addr6.sin6_port = htons(testport);
result = ::bind(sock, (const sockaddr*)&addr6, sizeof addr6);
result = ::bind(sock, reinterpret_cast<const sockaddr*>(&addr6), sizeof addr6);
if (result)
result = WSAGetLastError();
break;

View File

@ -11,11 +11,7 @@
#include <vector>
#include <algorithm>
#ifdef USE_RESIP_INTEGRATION
# include "resiprocate/rutil/ThreadIf.hxx"
#else
#include <thread>
#endif
#include "HL_NetworkSocket.h"
#include "HL_Sync.h"
@ -25,6 +21,7 @@
class SocketSink
{
public:
virtual ~SocketSink();
virtual void onReceivedData(PDatagramSocket socket, InternetAddress& src, const void* receivedPtr, unsigned receivedSize) = 0;
};
@ -73,7 +70,7 @@ protected:
SocketSink* mSink;
SocketItem()
:mSink(NULL)
:mSink(nullptr)
{ }
SocketItem(unsigned short portnumber, SocketSink* sink)

View File

@ -285,6 +285,10 @@ float Variant::asFloat() const
}
}
double Variant::asDouble() const
{
return static_cast<double>(asFloat());
}
std::string Variant::asStdString() const
{

View File

@ -61,6 +61,7 @@ public:
int64_t asInt64() const;
bool asBool() const;
float asFloat() const;
double asDouble() const;
std::string asStdString() const;
//const char* asString();
void* asPointer() const;

View File

@ -22,11 +22,6 @@
#include <map>
#if defined(USE_PVQA_LIBRARY)
# include "MT_SevanaMos.h"
#endif
// #define DUMP_DECODED
namespace MT

View File

@ -1,991 +0,0 @@
#define NOMINMAX
//#include "config.h"
#include "MT_SevanaMos.h"
#if defined(USE_PVQA_LIBRARY)
#if defined(TARGET_SERVER)
# include <filesystem>
#endif
#include "../engine/helper/HL_Log.h"
#include "../engine/helper/HL_CsvReader.h"
#include "../engine/helper/HL_String.h"
#include "../engine/audio/Audio_WavFile.h"
#include <assert.h>
#include <fstream>
#include <streambuf>
#include <iostream>
#include <atomic>
#include <algorithm>
#if defined(TARGET_SERVER)
extern std::string IntervalCacheDir;
#endif
#define LOG_SUBSYSTEM "Sevana"
#define PVQA_ECHO_DETECTOR_NAME "ECHO"
//#define PVQA_ECHO_DETECTOR_NAME "EchoM-00"
namespace MT {
#if !defined(MOS_BEST_COLOR)
# define MOS_BEST_COLOR 0x11FF11
# define MOS_BAD_COLOR 0x000000
#endif
#if defined(TARGET_WIN)
# define popen _popen
# define pclose _pclose
#endif
static std::string execCommand(const std::string& cmd)
{
std::cout << cmd << "\n";
std::shared_ptr<FILE> pipe(popen(cmd.c_str(), "r"), pclose);
if (!pipe)
throw std::runtime_error("Failed to run.");
char buffer[1024];
std::string result = "";
while (!feof(pipe.get()))
{
if (fgets(buffer, 1024, pipe.get()) != nullptr)
result += buffer;
}
return result;
}
// -------------- SevanaMosUtility --------------
void SevanaMosUtility::run(const std::string& pcmPath, const std::string& intervalPath,
std::string& estimation, std::string& intervals)
{
/*
#if defined(TARGET_SERVER)
path sevana = current_path() / "sevana";
#if defined(TARGET_LINUX) || defined(TARGET_OSX)
path exec = sevana / "pvqa";
#else
path exec = sevana / "pvqa.exe";
#endif
path lic = sevana / "pvqa.lic";
path cfg = sevana / "settings.cfg";
estimation.clear();
char cmdbuffer[1024];
sprintf(cmdbuffer, "%s %s analysis %s %s %s 0.799", exec.string().c_str(), lic.string().c_str(),
intervalPath.c_str(), cfg.string().c_str(), pcmPath.c_str());
std::string output = execCommand(cmdbuffer);
//ICELogDebug(<< "Got PVQA analyzer output: " << output);
std::string line;
std::istringstream is(output);
while (std::getline(is, line))
{
std::string::size_type mosPosition = line.find("MOS = ");
if ( mosPosition != std::string::npos)
{
estimation = line.substr(mosPosition + 6);
boost::algorithm::trim(estimation);
}
}
if (!estimation.size())
{
// Dump utility output if estimation failed
ICELogError(<< "PVQA failed with message: " << output);
return;
}
// Read intervals report file
if (boost::filesystem::exists(intervalPath) && !estimation.empty())
{
std::ifstream t(intervalPath);
std::string str((std::istreambuf_iterator<char>(t)),
std::istreambuf_iterator<char>());
intervals = str;
}
#endif
*/
}
float getSevanaMos(const std::string& audioPath, const std::string& intervalReportPath,
std::string& intervalReport)
{
return 0.0f;
/*
// Find Sevana MOS estimation
ICELogDebug( << "Running MOS utitlity on resulted PCM file " << audioPath );
try
{
std::string buffer;
SevanaMosUtility::run(audioPath, intervalReportPath, buffer, intervalReport);
ICELogDebug( << "MOS utility is finished on PCM file " << audioPath );
return (float)atof(buffer.c_str());
}
catch(std::exception& e)
{
ICELogError( << "MOS utility failed on PCM file " << audioPath << ". Error msg: " << e.what() );
return 0.0;
}*/
}
// ------------------- SevanaPVQA -------------------
void* SevanaPVQA::mLibraryConfiguration = nullptr;
int SevanaPVQA::mLibraryErrorCode = 0;
std::atomic_int SevanaPVQA::mInstanceCounter;
std::atomic_uint_least64_t SevanaPVQA::mAllProcessedMilliseconds;
bool SevanaPVQA::mPvqaLoaded = false;
std::string SevanaPVQA::getVersion()
{
return PVQA_GetVersion();
}
#if defined(TARGET_ANDROID)
void SevanaPVQA::setupAndroidEnvironment(void *environment, void *appcontext)
{
PVQA_SetupAndroidEnvironment(environment, appcontext);
}
#endif
bool SevanaPVQA::initializeLibrary(const std::string& pathToLicenseFile, const std::string& pathToConfigFile)
{
mPvqaLoaded = false;
ICELogInfo(<< "Sevana PVQA is about to be initialized.");
// Initialize PVQA library
if (!mLibraryConfiguration)
{
mInstanceCounter = 0;
mLibraryErrorCode = PVQA_InitLib(const_cast<char*>(pathToLicenseFile.c_str()));
if (mLibraryErrorCode)
{
ICELogError(<< "Problem when initializing PVQA library. Error code: " << mLibraryErrorCode
<< ". Path to license file is " << pathToLicenseFile
<< ". Path to config file is " << pathToConfigFile);
return false;
}
if (pathToConfigFile.size())
{
mLibraryConfiguration = PVQA_LoadCFGFile(const_cast<char*>(pathToConfigFile.c_str()), &mLibraryErrorCode);
if (!mLibraryConfiguration)
{
PVQA_ReleaseLib();
ICELogError(<< "Problem with PVQA configuration file.");
return false;
}
}
mPvqaLoaded = true;
}
return true;
}
bool SevanaPVQA::initializeLibraryWithData(const void* license_buffer, size_t license_len,
const void* config_buffer, size_t config_len)
{
mPvqaLoaded = false;
#if defined(OLD_PVQA)
return false;
#else
ICELogInfo(<< "Sevana PVQA is about to be initialized via byte buffers.");
// Initialize PVQA library
if (!mLibraryConfiguration)
{
mInstanceCounter = 0;
mLibraryErrorCode = PVQA_InitLibWithLicData(license_buffer, license_len);
if (mLibraryErrorCode)
{
ICELogError(<< "Problem when initializing PVQA library. Error code: " << mLibraryErrorCode);
return false;
}
if (config_buffer && config_len)
{
mLibraryConfiguration = PVQA_LoadCFGData(config_buffer, config_len, &mLibraryErrorCode);
if (!mLibraryConfiguration)
{
PVQA_ReleaseLib();
ICELogError(<< "Problem with PVQA configuration file.");
return false;
}
}
mPvqaLoaded = true;
}
return true;
#endif
}
bool SevanaPVQA::isInitialized()
{
return mPvqaLoaded;
}
int SevanaPVQA::getLibraryError()
{
return mLibraryErrorCode;
}
void SevanaPVQA::releaseLibrary()
{
PVQA_ReleaseLib();
}
SevanaPVQA::SevanaPVQA()
{
}
SevanaPVQA::~SevanaPVQA()
{
close();
}
void SevanaPVQA::open(double interval, Model model)
{
if (!isInitialized())
{
ICELogError(<< "PVQA library is not initialized.");
return;
}
if (mOpenFailed)
{
ICELogError(<< "Open failed already, reject this attempt.");
return;
}
if (mContext)
{
ICELogError(<< "Already opened (context is not nullptr).");
return;
}
ICELogDebug(<<"Attempt to create PVQA instance.");
mProcessedSamples = 0;
mModel = model;
mIntervalLength = interval;
mAudioLineInitialized = false;
mContext = PVQA_CreateAudioQualityAnalyzer(mLibraryConfiguration);
if (!mContext)
{
ICELogError(<< "Failed to create PVQA instance. Instance counter: " << mInstanceCounter);
mOpenFailed = true;
return;
}
mInstanceCounter++;
int rescode = 0;
rescode = PVQA_AudioQualityAnalyzerSetIntervalLength(mContext, interval);
if (rescode)
{
ICELogError(<< "Failed to set interval length on PVQA instance. Result code: " << rescode);
close();
mOpenFailed = true;
return;
}
if (mModel == Model::Stream)
{
rescode = PVQA_OnStartStreamData(mContext);
if (rescode)
{
ICELogError(<< "Failed to start streaming analysis on PVQA instance. Result code: " << rescode);
close();
mOpenFailed = true;
return;
}
}
ICELogDebug(<<"PVQA instance is created. Instance counter: " << mInstanceCounter);
}
void SevanaPVQA::close()
{
if (mContext)
{
ICELogDebug(<< "Attempt to destroy PVQA instance.");
PVQA_ReleaseAudioQualityAnalyzer(mContext);
mInstanceCounter--;
ICELogDebug(<< "PVQA instance destroyed. Current instance counter: " << mInstanceCounter);
mContext = nullptr;
mOpenFailed = false;
}
}
bool SevanaPVQA::isOpen() const
{
return mContext != nullptr;
}
void SevanaPVQA::update(int samplerate, int channels, const void *pcmBuffer, int pcmLength)
{
if (!mContext)
{
ICELogError(<< "No PVQA context.");
return;
}
// Model is assert here as it can be any if context is not created.
assert (mModel == Model::Stream);
TPVQA_AudioItem item;
item.dNChannels = channels;
item.dSampleRate = samplerate;
item.dNSamples = pcmLength / 2 / channels;
item.pSamples = (short*)pcmBuffer;
int rescode = PVQA_OnAddStreamAudioData(mContext, &item);
if (rescode)
{
ICELogError(<< "Failed to stream data to PVQA instance. Result code: " << rescode);
}
int milliseconds = pcmLength / 2 / channels / (samplerate / 1000);
mProcessedMilliseconds += milliseconds;
mAllProcessedMilliseconds += milliseconds;
}
SevanaPVQA::DetectorsList SevanaPVQA::getDetectorsNames(const std::string& report)
{
DetectorsList result;
if (!report.empty())
{
std::istringstream iss(report);
CsvReader reader(iss);
reader.readLine(result.mNames);
result.mStartIndex = 2;
// Remove first columns
if (result.mStartIndex < (int)result.mNames.size() - 1)
{
result.mNames.erase(result.mNames.begin(), result.mNames.begin() + result.mStartIndex);
// Remove last column
result.mNames.erase(result.mNames.begin() + result.mNames.size() - 1);
for (auto& name: result.mNames)
name = StringHelper::trim(name);
}
}
return result;
}
float SevanaPVQA::getResults(std::string& report, EchoData** echo, int samplerate, Codec codec)
{
if (!mContext)
{
ICELogError(<< "No PVQA context.");
return 0.0;
}
if (mModel == Model::Stream)
{
if (mProcessedMilliseconds == 0)
{
ICELogError(<< "No audio in PVQA.");
return -1;
}
if (PVQA_OnFinalizeStream(mContext, (long)samplerate))
{
ICELogError(<< "Failed to finalize results from PVQA.");
return -1;
}
ICELogInfo(<< "Processed " << mProcessedMilliseconds << " milliseconds.");
}
TPVQA_Results results;
if (PVQA_FillQualityResultsStruct(mContext, &results))
{
ICELogError(<< "Failed to get results from PVQA.");
return -1;
}
int reportLength = PVQA_GetQualityStringSize(mContext);
if (reportLength)
{
char* buffer = (char*)alloca(reportLength + 1);
if (PVQA_FillQualityString(mContext, buffer))
{
ICELogError(<< "Failed to fill intervals report.");
}
else
report = buffer;
}
#if defined(TARGET_LINUX) && defined(PVQA_WITH_ECHO_DATA)
if (mModel == SevanaPVQA::Model::Stream && echo)
{
// Return echo detector counters
// Get list of names for echo detector - for debugging only
std::vector<std::string> names;
int errCode = 0;
const char** iNames = (const char **)PVQA_GetProcessorValuesNamesList(mContext, PVQA_ECHO_DETECTOR_NAME, &errCode);
if (!errCode && iNames)
{
int nameIndex = 0;
for(const char * locName = iNames[nameIndex]; locName; locName = iNames[++nameIndex])
names.push_back(locName);
// Get values for echo detector
PVQA_Array2D* array = PVQA_GetProcessorValuesList(mContext, PVQA_ECHO_DETECTOR_NAME, 0, mProcessedMilliseconds, "values", &errCode);
if (array)
{
*echo = new std::vector<std::vector<float>>();
for (int r = 0; r < array->rows; r++)
{
std::vector<float> row;
for (int c = 0; c < array->columns; c++)
row.push_back(array->data[r * array->columns + c]);
(*echo)->push_back(row);
}
PVQA_ReleaseArray2D(array); array = nullptr;
}
// For debugging only
/*if (*echo)
{
for (const auto& row: **echo)
{
std::cout << "<";
for (const auto& v: row)
std::cout << v << " ";
std::cout << ">" << std::endl;
}
}*/
// No need to delete maxValues - it will be deleted on PVQA analyzer context freeing.
}
}
#endif
// Limit maximal value of MOS depending on codec
float result = (float)results.dMOSLike;
float mv = 5.0f;
switch (codec)
{
case Codec::G711: mv = 4.1f; break;
case Codec::G729: mv = 3.92f; break;
default:
mv = 5.0;
}
return std::min(result, mv);
}
void SevanaPVQA::setPathToDumpFile(const std::string& path)
{
mDumpWavPath = path;
}
float SevanaPVQA::process(int samplerate, int channels, const void *pcmBuffer, int pcmLength, std::string &report, Codec codec)
{
//std::cout << "Sent " << pcmLength << " bytes of audio to analyzer." << std::endl;
assert (mModel == Model::Interval);
if (!mContext)
return 0.0;
/*if (!mAudioLineInitialized)
{
mAudioLineInitialized = true;
if (PVQA_AudioQualityAnalyzerCreateDelayLine(mContext, samplerate, channels, 20))
ICELogError(<< "Failed to create delay line.");
}*/
TPVQA_AudioItem item;
item.dNChannels = channels;
item.dSampleRate = samplerate;
item.dNSamples = pcmLength / 2 / channels;
item.pSamples = (short*)pcmBuffer;
//std::cout << "Sending chunk of audio with rate = " << samplerate << ", channels = " << channels << ", number of samples " << item.dNSamples << std::endl;
/*
if (!mDumpWavPath.empty())
{
WavFileWriter writer;
writer.open(mDumpWavPath, samplerate, channels);
writer.write(item.pSamples, item.dNSamples * 2 * channels);
writer.close();
ICELogError(<< "Sending chunk of audio with rate = " << samplerate << ", channels = " << channels << ", number of samples " << item.dNSamples);
}
*/
int code = PVQA_OnTestAudioData(mContext, &item);
if (code)
{
ICELogError(<< "Failed to run PVQA on audio buffer with code " << code);
return 0.0;
}
/*
if (item.pSamples != pcmBuffer || item.dNSamples != pcmLength / 2 / channels || item.dSampleRate != samplerate || item.dNChannels != channels)
{
ICELogError(<< "PVQA changed input parameters!!!!");
}
*/
// Increase counter of processed samples
mProcessedSamples += pcmLength / channels / 2;
int milliseconds = pcmLength / channels / 2 / (samplerate / 1000);
mProcessedMilliseconds += milliseconds;
// Overall counter
mAllProcessedMilliseconds += milliseconds;
// Get results
return getResults(report, nullptr, samplerate, codec);
}
struct RgbColor
{
uint8_t mRed = 0;
uint8_t mGreen = 0;
uint8_t mBlue = 0;
static RgbColor parse(uint32_t rgb)
{
RgbColor result;
result.mBlue = (uint8_t)(rgb & 0xff);
result.mGreen = (uint8_t)((rgb >> 8) & 0xff);
result.mRed = (uint8_t)((rgb >> 16) & 0xff);
return result;
}
std::string toHex() const
{
char result[7];
sprintf(result, "%02x%02x%02x", int(mRed), int(mGreen), int(mBlue));
return std::string(result);
}
};
int SevanaPVQA::getSize() const
{
int result = 0;
result += sizeof(*this);
// TODO: add PVQA analyzer size
return result;
}
std::string SevanaPVQA::mosToColor(float mos)
{
// Limit MOS value by 5.0
mos = mos > 5.0f ? 5.0f : mos;
mos = mos < 1.0f ? 1.0f : mos;
// Split to components
RgbColor start = RgbColor::parse(MOS_BEST_COLOR), end = RgbColor::parse(MOS_BAD_COLOR);
float mosFraction = (mos - 1.0f) / 4.0f;
end.mBlue += (uint8_t)((start.mBlue - end.mBlue) * mosFraction);
end.mGreen += (uint8_t)((start.mGreen - end.mGreen) * mosFraction);
end.mRed += (uint8_t)((start.mRed - end.mRed) * mosFraction);
return end.toHex();
}
} // end of namespace MT
#endif
#if defined(USE_AQUA_LIBRARY)
#include <string.h>
#include "helper/HL_String.h"
#include <sstream>
#include <json/json.h>
namespace MT
{
int SevanaAqua::initializeLibrary(const std::string& pathToLicenseFile)
{
//char buffer[pathToLicenseFile.length() + 1];
//strcpy(buffer, pathToLicenseFile.c_str());
return SSA_InitLib(const_cast<char*>(pathToLicenseFile.data()));
}
int SevanaAqua::initializeLibrary(const void* buffer, size_t len)
{
return SSA_InitLibWithData(buffer, len);
}
void SevanaAqua::releaseLibrary()
{
SSA_ReleaseLib();
}
std::string SevanaAqua::FaultsReport::toText() const
{
std::ostringstream oss;
if (mSignalAdvancedInMilliseconds > -4999.0)
oss << "Signal advanced in milliseconds: " << mSignalAdvancedInMilliseconds << std::endl;
if (mMistimingInPercents > -4999.0)
oss << "Mistiming in percents: " << mMistimingInPercents << std::endl;
for (ResultMap::const_iterator resultIter = mResultMap.begin(); resultIter != mResultMap.end(); resultIter++)
{
oss << resultIter->first << ":\t\t\t" << resultIter->second.mSource << " : \t" << resultIter->second.mDegrated << " \t" << resultIter->second.mUnit << std::endl;
}
return oss.str();
}
Json::Value SevanaAqua::FaultsReport::toJson() const
{
std::ostringstream oss;
Json::Value result;
result["Mistiming"] = mMistimingInPercents;
result["SignalAdvanced"] = mSignalAdvancedInMilliseconds;
Json::Value items;
for (ResultMap::const_iterator resultIter = mResultMap.begin(); resultIter != mResultMap.end(); resultIter++)
{
Json::Value item;
item["name"] = resultIter->first;
item["source"] = resultIter->second.mSource;
item["degrated"] = resultIter->second.mDegrated;
item["unit"] = resultIter->second.mUnit;
items.append(item);
}
result["items"] = items;
return result;
}
std::string SevanaAqua::getVersion()
{
TSSA_AQuA_Info* info = SSA_GetPAQuAInfo();
if (info)
return info->dVersionString;
return "";
}
SevanaAqua::SevanaAqua()
{
open();
}
SevanaAqua::~SevanaAqua()
{
close();
}
void SevanaAqua::open()
{
std::unique_lock<std::mutex> l(mMutex);
if (mContext)
return;
mContext = SSA_CreateAudioQualityAnalyzer();
if (!mContext)
;
//setParam("OutputFormats", "json");
}
void SevanaAqua::close()
{
std::unique_lock<std::mutex> l(mMutex);
if (!mContext)
return;
SSA_ReleaseAudioQualityAnalyzer(mContext);
mContext = nullptr;
}
bool SevanaAqua::isOpen() const
{
return mContext != nullptr;
}
void SevanaAqua::setTempPath(const std::string& temp_path)
{
mTempPath = temp_path;
}
std::string SevanaAqua::getTempPath() const
{
return mTempPath;
}
SevanaAqua::CompareResult SevanaAqua::compare(AudioBuffer& reference, AudioBuffer& test)
{
// Clear previous temporary file
if (!mTempPath.empty())
::remove(mTempPath.c_str());
// Result value
CompareResult r;
std::unique_lock<std::mutex> l(mMutex);
if (!mContext || !reference.isInitialized() || !test.isInitialized())
return r;
// Make analysis
TSSA_AQuA_AudioData aad;
aad.dSrcData.dNChannels = reference.mChannels;
aad.dSrcData.dSampleRate = reference.mRate;
aad.dSrcData.pSamples = (short*)reference.mData->data();
aad.dSrcData.dNSamples = (long)reference.mData->size() / 2 / reference.mChannels;
aad.dTstData.dNChannels = test.mChannels;
aad.dTstData.dSampleRate = test.mRate;
aad.dTstData.pSamples = (short*)test.mData->data();
aad.dTstData.dNSamples = (long)test.mData->size() / 2 / test.mChannels;
int rescode;
rescode = SSA_OnTestAudioData(mContext, &aad);
if (rescode)
return r;
// Get results
int len = SSA_GetQualityStringSize(mContext);
char* qs = (char*)alloca(len + 10);
SSA_FillQualityString(mContext, qs);
//std::cout << qs << std::endl;
std::istringstream iss(qs);
while (!iss.eof())
{
std::string l;
std::getline(iss, l);
// Split by :
std::vector<std::string> p;
StringHelper::split(l, p, "\t");
if (p.size() == 3)
{
p[1] = StringHelper::trim(p[1]);
p[2] = StringHelper::trim(p[2]);
r.mReport[p[1]] = p[2];
}
}
len = SSA_GetSrcSignalSpecSize(mContext);
float* srcSpecs = new float[len];
SSA_FillSrcSignalSpecArray(mContext, srcSpecs);
Json::Value src_spec_signal;
for(int i=0; i<16 && i<len; i++)
src_spec_signal.append(srcSpecs[i]);
delete[] srcSpecs;
r.mReport["SrcSpecSignal"] = src_spec_signal;
len = SSA_GetTstSignalSpecSize(mContext);
float* tstSpecs = new float[len];
SSA_FillTstSignalSpecArray(mContext, tstSpecs);
Json::Value tst_spec_signal;
for(int i=0; i<16 && i<len; i++)
tst_spec_signal.append(tstSpecs[i]);
r.mReport["TstSpecSignal"] = tst_spec_signal;
delete[] tstSpecs;
char* faults_str = nullptr;
int faults_str_len = 0;
if (mTempPath.empty())
{
faults_str_len = SSA_GetFaultsAnalysisStringSize(mContext);
if (faults_str_len > 0) {
faults_str = new char[faults_str_len + 1];
SSA_FillFaultsAnalysisString(mContext, faults_str);
faults_str[faults_str_len] = 0;
}
}
char* pairs_str = nullptr;
int pairs_str_len = SSA_GetSpecPairsStringSize(mContext);
if (pairs_str_len > 0)
{
char *pairs_str = new char[pairs_str_len + 1];
SSA_FillSpecPairsString(mContext, pairs_str, pairs_str_len);
pairs_str[pairs_str_len] = 0;
}
TSSA_AQuA_Results iResults;
SSA_FillQualityResultsStruct(mContext, &iResults);
r.mReport["dPercent"] = iResults.dPercent;
r.mReport["dMOSLike"] = iResults.dMOSLike;
if (faults_str_len > 0)
{
std::istringstream iss(faults_str);
r.mFaults = loadFaultsReport(iss);
}
else
if (!mTempPath.empty())
{
std::ifstream ifs(mTempPath.c_str());
r.mFaults = loadFaultsReport(ifs);
}
delete[] faults_str; faults_str = nullptr;
delete[] pairs_str; pairs_str = nullptr;
r.mMos = (float)iResults.dMOSLike;
return r;
}
void SevanaAqua::configureWith(const Config& config)
{
if (!mContext)
return;
for (auto& item: config)
{
const std::string& name = item.first;
const std::string& value = item.second;
if (!SSA_SetAnyString(mContext, const_cast<char *>(name.c_str()), const_cast<char *>(value.c_str())))
throw std::runtime_error(std::string("SSA_SetAnyString returned failed for pair ") + name + " " + value);
}
}
SevanaAqua::Config SevanaAqua::parseConfig(const std::string& line)
{
Config result;
// Split command line to parts
std::vector<std::string> pl;
StringHelper::split(line, pl, "-");
for (const std::string& s: pl)
{
std::string::size_type p = s.find(' ');
if (p != std::string::npos)
{
std::string name = StringHelper::trim(s.substr(0, p));
std::string value = StringHelper::trim(s.substr(p + 1));
result[name] = value;
}
}
return result;
}
SevanaAqua::PFaultsReport SevanaAqua::loadFaultsReport(std::istream& input)
{
PFaultsReport result = std::make_shared<FaultsReport>();
std::string line;
std::vector<std::string> parts;
// Parse output
while (!input.eof())
{
std::getline(input, line);
if (line.size() < 3)
continue;
std::string::size_type p = line.find(":");
if (p != std::string::npos)
{
std::string name = StringHelper::trim(line.substr(0, p));
FaultsReport::Result r;
// Split report line to components
parts.clear();
StringHelper::split(line.substr(p + 1), parts, " \t");
// Remove empty components
parts.erase(std::remove_if(parts.begin(), parts.end(), [](const std::string& item){return item.empty();}), parts.end());
if (parts.size() >= 2)
{
r.mSource = parts[0];
r.mDegrated = parts[1];
if (parts.size()> 2)
r.mUnit = parts[2];
result->mResultMap[name] = r;
}
}
else
{
p = line.find("ms.");
if (p != std::string::npos)
{
parts.clear();
StringHelper::split(line, parts, " \t");
if (parts.size() >= 3)
{
if (parts.back() == "ms.")
result->mSignalAdvancedInMilliseconds = (float)std::atof(parts[parts.size() - 2].c_str());
}
}
else
{
p = line.find("percent.");
if (p != std::string::npos)
{
parts.clear();
StringHelper::split(line, parts, " \t");
if (parts.size() >= 3)
{
if (parts.back() == "percent.")
result->mMistimingInPercents = (float)std::atof(parts[parts.size() - 2].c_str());
}
}
}
}
}
return result;
}
} // end of namespace MT
// It is to workaround old AQuA NDK build - it has reference to ftime
/*#if defined(TARGET_ANDROID)
#include <sys/timeb.h>
// This was removed from POSIX 2008.
int ftime(struct timeb* tb) {
struct timeval tv;
struct timezone tz;
if (gettimeofday(&tv, &tz) < 0)
return -1;
tb->time = tv.tv_sec;
tb->millitm = (tv.tv_usec + 500) / 1000;
if (tb->millitm == 1000) {
++tb->time;
tb->millitm = 0;
}
tb->timezone = tz.tz_minuteswest;
tb->dstflag = tz.tz_dsttime;
return 0;
}
#endif*/
#endif

View File

@ -1,247 +0,0 @@
#ifndef _SEVANA_MOS_H
#define _SEVANA_MOS_H
#include <string>
#include <vector>
#include <memory>
#include <mutex>
#include <atomic>
#include <map>
#include <memory.h>
#if defined(USE_PVQA_LIBRARY)
# include "pvqa.h"
# if !defined(PVQA_INTERVAL)
# define PVQA_INTERVAL (0.68)
# endif
#endif
#if defined(USE_AQUA_LIBRARY)
# include "aqua.h"
# include <json/json.h>
#endif
# include "helper/HL_ByteBuffer.h"
namespace MT
{
enum class ReportType
{
PlainText,
Html
};
#if defined(USE_PVQA_LIBRARY)
class SevanaMosUtility
{
public:
// Returns MOS estimation as text representation of float value or "failed" word.
static void run(const std::string& pcmPath, const std::string& intervalPath,
std::string& estimation, std::string& intervals);
};
extern float getSevanaMos(const std::string& audioPath, const std::string& intervalReportPath,
std::string& intervalReport);
class SevanaPVQA
{
public:
enum class Model
{
Stream,
Interval
};
protected:
static void* mLibraryConfiguration;
static int mLibraryErrorCode;
static std::atomic_int mInstanceCounter;
static std::atomic_uint_least64_t mAllProcessedMilliseconds;
static bool mPvqaLoaded;
void* mContext = nullptr;
Model mModel = Model::Interval;
double mIntervalLength = 0.68;
uint64_t mProcessedSamples = 0,
mProcessedMilliseconds = 0;
bool mAudioLineInitialized = false;
std::string mDumpWavPath;
bool mOpenFailed = false;
ByteBuffer mAudioDump;
public:
static std::string getVersion();
// Required to call before any call to SevanaPVQA instance methods
#if defined(TARGET_ANDROID)
static void setupAndroidEnvironment(void* environment, void* appcontext);
#endif
// Path to config file can be empty
// In this case library will be considered initialized (but will produce zero MOS)
static bool initializeLibrary(const std::string& pathToLicenseFile, const std::string& pathToConfigFile);
static bool initializeLibraryWithData(const void* license_buffer, size_t license_len,
const void* config_buffer, size_t config_len);
static bool isInitialized();
static int getLibraryError();
static void releaseLibrary();
static int getInstanceCounter() { return mInstanceCounter; }
static uint64_t getProcessedMilliseconds() { return mAllProcessedMilliseconds; }
SevanaPVQA();
~SevanaPVQA();
void open(double interval, Model model);
void close();
bool isOpen() const;
// Update/Get model
void update(int samplerate, int channels, const void* pcmBuffer, int pcmLength);
typedef std::vector<std::vector<float>> EchoData;
enum class Codec
{
None,
G711,
ILBC,
G722,
G729,
GSM,
AMRNB,
AMRWB,
OPUS
};
float getResults(std::string& report, EchoData** echo, int samplerate, Codec codec);
// Report is interval report. Names are output detector names. startIndex is column's start index in interval report of first detector.
struct DetectorsList
{
std::vector<std::string> mNames;
int mStartIndex = 0;
};
static DetectorsList getDetectorsNames(const std::string& report);
// Get MOS in one shot
void setPathToDumpFile(const std::string& path);
float process(int samplerate, int channels, const void* pcmBuffer, int pcmLength, std::string& report, Codec codec);
Model getModel() const { return mModel; }
int getSize() const;
static std::string mosToColor(float mos);
};
typedef std::shared_ptr<SevanaPVQA> PSevanaPVQA;
#endif
#if defined(USE_AQUA_LIBRARY)
class SevanaAqua
{
protected:
void* mContext = nullptr;
std::mutex mMutex;
std::string mTempPath;
public:
// Returns 0 (zero) on successful initialization, otherwise it is error code
static int initializeLibrary(const std::string& pathToLicenseFile);
static int initializeLibrary(const void* buffer, size_t len);
static void releaseLibrary();
static std::string getVersion();
SevanaAqua();
~SevanaAqua();
void open();
void close();
bool isOpen() const;
void setTempPath(const std::string& temp_path);
std::string getTempPath() const;
typedef std::map<std::string, std::string> Config;
void configureWith(const Config& config);
static Config parseConfig(const std::string& line);
// Report is returned in JSON format
struct AudioBuffer
{
AudioBuffer()
{}
AudioBuffer(int size)
{
mData = std::make_shared<std::vector<unsigned char>>();
mData->resize(size);
}
AudioBuffer(const void* data, int size)
{
mData = std::make_shared<std::vector<unsigned char>>();
mData->resize(size);
memcpy(mData->data(), data, size);
}
void* data()
{
return mData ? mData->data() : nullptr;
}
const void* data() const
{
return mData ? mData->data() : nullptr;
}
int size() const
{
return mData ? mData->size() : 0;
}
int mRate = 8000;
int mChannels = 1;
std::shared_ptr<std::vector<unsigned char>> mData;
bool isInitialized() const { return mRate > 0 && mChannels > 0 && mData; }
};
struct FaultsReport
{
float mSignalAdvancedInMilliseconds = -5000.0;
float mMistimingInPercents = -5000.0;
struct Result
{
std::string mSource, mDegrated, mUnit;
};
typedef std::map<std::string, Result> ResultMap;
ResultMap mResultMap;
Json::Value toJson() const;
std::string toText() const;
};
typedef std::shared_ptr<FaultsReport> PFaultsReport;
static PFaultsReport loadFaultsReport(std::istream& input);
// Compare in one shot. Report will include text representation of json report.
struct CompareResult
{
float mMos = 0.0f;
Json::Value mReport;
PFaultsReport mFaults;
};
CompareResult compare(AudioBuffer& reference, AudioBuffer& test);
};
typedef std::shared_ptr<SevanaAqua> PSevanaAqua;
#endif
}
#endif

View File

@ -9,8 +9,6 @@
#include "jrtplib/src/rtptimeutilities.h"
#include "jrtplib/src/rtppacket.h"
#include "MT_SevanaMos.h"
using std::experimental::optional;
namespace MT
@ -79,8 +77,8 @@ class JitterStatistics
{
public:
void process(jrtplib::RTPPacket* packet, int samplerate);
ProbeStats<double> get() const { return mJitter; }
double getMaxDelta() const { return mMaxDelta; }
ProbeStats<float> get() const { return mJitter; }
float getMaxDelta() const { return mMaxDelta; }
protected:
// Jitter calculation
@ -90,13 +88,13 @@ protected:
uint32_t mReceiveTimestamp = 0;
// It is classic jitter value in units
optional<double> mLastJitter;
optional<float> mLastJitter;
// Some statistics for jitter value in seconds
ProbeStats<double> mJitter;
ProbeStats<float> mJitter;
// Maximal delta in seconds
double mMaxDelta = 0.0;
float mMaxDelta = 0.0f;
};
class Statistics

View File

@ -21,10 +21,6 @@
#include <chrono>
#include "../helper/HL_Optional.hpp"
#if defined(USE_PVQA_LIBRARY)
# include "MT_SevanaMos.h"
#endif
using std::experimental::optional;
namespace MT