rtphone/src/engine/media/MT_SevanaMos.cpp

981 lines
26 KiB
C++

#define NOMINMAX
//#include "config.h"
#include "MT_SevanaMos.h"
#if defined(USE_PVQA_LIBRARY)
#if defined(TARGET_SERVER)
# include <boost/filesystem.hpp>
# include <boost/algorithm/string.hpp>
using namespace boost::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)
{
// 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;
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;
}
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.0;
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