/* * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "webrtc/modules/rtp_rtcp/test/BWEStandAlone/MatlabPlot.h" #include #include #include #include #ifdef MATLAB #include "engine.h" #endif #include "webrtc/system_wrappers/include/critical_section_wrapper.h" #include "webrtc/system_wrappers/include/event_wrapper.h" #include "webrtc/system_wrappers/include/tick_util.h" using namespace webrtc; #ifdef MATLAB MatlabEngine eng; MatlabLine::MatlabLine(int maxLen /*= -1*/, const char *plotAttrib /*= NULL*/, const char *name /*= NULL*/) : _xArray(NULL), _yArray(NULL), _maxLen(maxLen), _plotAttribute(), _name() { if (_maxLen > 0) { _xArray = mxCreateDoubleMatrix(1, _maxLen, mxREAL); _yArray = mxCreateDoubleMatrix(1, _maxLen, mxREAL); } if (plotAttrib) { _plotAttribute = plotAttrib; } if (name) { _name = name; } } MatlabLine::~MatlabLine() { if (_xArray != NULL) { mxDestroyArray(_xArray); } if (_yArray != NULL) { mxDestroyArray(_yArray); } } void MatlabLine::Append(double x, double y) { if (_maxLen > 0 && _xData.size() > static_cast(_maxLen)) { _xData.resize(_maxLen); _yData.resize(_maxLen); } _xData.push_front(x); _yData.push_front(y); } // append y-data with running integer index as x-data void MatlabLine::Append(double y) { if (_xData.empty()) { // first element is index 0 Append(0, y); } else { // take last x-value and increment double temp = _xData.back(); // last x-value Append(temp + 1, y); } } void MatlabLine::SetMaxLen(int maxLen) { if (maxLen <= 0) { // means no maxLen _maxLen = -1; } else { _maxLen = maxLen; if (_xArray != NULL) { mxDestroyArray(_xArray); mxDestroyArray(_yArray); } _xArray = mxCreateDoubleMatrix(1, _maxLen, mxREAL); _yArray = mxCreateDoubleMatrix(1, _maxLen, mxREAL); maxLen = ((unsigned int)maxLen <= _xData.size()) ? maxLen : (int)_xData.size(); _xData.resize(maxLen); _yData.resize(maxLen); //// reserve the right amount of memory //_xData.reserve(_maxLen); //_yData.reserve(_maxLen); } } void MatlabLine::SetAttribute(char *plotAttrib) { _plotAttribute = plotAttrib; } void MatlabLine::SetName(char *name) { _name = name; } void MatlabLine::GetPlotData(mxArray** xData, mxArray** yData) { // Make sure we have enough Matlab allocated memory. // Assuming both arrays (x and y) are of the same size. if (_xData.empty()) { return; // No data } unsigned int size = 0; if (_xArray != NULL) { size = (unsigned int)mxGetNumberOfElements(_xArray); } if (size < _xData.size()) { if (_xArray != NULL) { mxDestroyArray(_xArray); mxDestroyArray(_yArray); } _xArray = mxCreateDoubleMatrix(1, _xData.size(), mxREAL); _yArray = mxCreateDoubleMatrix(1, _yData.size(), mxREAL); } if (!_xData.empty()) { double* x = mxGetPr(_xArray); std::list::iterator it = _xData.begin(); for (int i = 0; it != _xData.end(); it++, i++) { x[i] = *it; } } if (!_yData.empty()) { double* y = mxGetPr(_yArray); std::list::iterator it = _yData.begin(); for (int i = 0; it != _yData.end(); it++, i++) { y[i] = *it; } } *xData = _xArray; *yData = _yArray; } std::string MatlabLine::GetXName() { std::ostringstream xString; xString << "x_" << _name; return xString.str(); } std::string MatlabLine::GetYName() { std::ostringstream yString; yString << "y_" << _name; return yString.str(); } std::string MatlabLine::GetPlotString() { std::ostringstream s; if (_xData.size() == 0) { s << "[0 1], [0 1]"; // To get an empty plot } else { s << GetXName() << "(1:" << _xData.size() << "),"; s << GetYName() << "(1:" << _yData.size() << ")"; } s << ", '"; s << _plotAttribute; s << "'"; return s.str(); } std::string MatlabLine::GetRefreshString() { std::ostringstream s; if (_xData.size() > 0) { s << "set(h,'xdata',"<< GetXName() <<"(1:" << _xData.size() << "),'ydata',"<< GetYName() << "(1:" << _yData.size() << "));"; } else { s << "set(h,'xdata',[NaN],'ydata',[NaN]);"; } return s.str(); } std::string MatlabLine::GetLegendString() { return ("'" + _name + "'"); } bool MatlabLine::hasLegend() { return (!_name.empty()); } // remove data points, but keep attributes void MatlabLine::Reset() { _xData.clear(); _yData.clear(); } void MatlabLine::UpdateTrendLine(MatlabLine * sourceData, double slope, double offset) { Reset(); // reset data, not attributes and name double thexMin = sourceData->xMin(); double thexMax = sourceData->xMax(); Append(thexMin, thexMin * slope + offset); Append(thexMax, thexMax * slope + offset); } double MatlabLine::xMin() { if (!_xData.empty()) { std::list::iterator theStart = _xData.begin(); std::list::iterator theEnd = _xData.end(); return(*min_element(theStart, theEnd)); } return (0.0); } double MatlabLine::xMax() { if (!_xData.empty()) { std::list::iterator theStart = _xData.begin(); std::list::iterator theEnd = _xData.end(); return(*max_element(theStart, theEnd)); } return (0.0); } double MatlabLine::yMin() { if (!_yData.empty()) { std::list::iterator theStart = _yData.begin(); std::list::iterator theEnd = _yData.end(); return(*min_element(theStart, theEnd)); } return (0.0); } double MatlabLine::yMax() { if (!_yData.empty()) { std::list::iterator theStart = _yData.begin(); std::list::iterator theEnd = _yData.end(); return(*max_element(theStart, theEnd)); } return (0.0); } MatlabTimeLine::MatlabTimeLine(int horizonSeconds /*= -1*/, const char *plotAttrib /*= NULL*/, const char *name /*= NULL*/, int64_t refTimeMs /* = -1*/) : _timeHorizon(horizonSeconds), MatlabLine(-1, plotAttrib, name) // infinite number of elements { if (refTimeMs < 0) _refTimeMs = TickTime::MillisecondTimestamp(); else _refTimeMs = refTimeMs; } void MatlabTimeLine::Append(double y) { MatlabLine::Append(static_cast(TickTime::MillisecondTimestamp() - _refTimeMs) / 1000.0, y); PurgeOldData(); } void MatlabTimeLine::PurgeOldData() { if (_timeHorizon > 0) { // remove old data double historyLimit = static_cast(TickTime::MillisecondTimestamp() - _refTimeMs) / 1000.0 - _timeHorizon; // remove data points older than this std::list::reverse_iterator ritx = _xData.rbegin(); uint32_t removeCount = 0; while (ritx != _xData.rend()) { if (*ritx >= historyLimit) { break; } ritx++; removeCount++; } if (removeCount == 0) { return; } // remove the range [begin, it). //if (removeCount > 10) //{ // printf("Removing %lu elements\n", removeCount); //} _xData.resize(_xData.size() - removeCount); _yData.resize(_yData.size() - removeCount); } } int64_t MatlabTimeLine::GetRefTime() { return(_refTimeMs); } MatlabPlot::MatlabPlot() : _figHandle(-1), _smartAxis(false), _critSect(CriticalSectionWrapper::CreateCriticalSection()), _timeToPlot(false), _plotting(false), _enabled(true), _firstPlot(true), _legendEnabled(true), _donePlottingEvent(EventWrapper::Create()) { CriticalSectionScoped cs(_critSect); _xlim[0] = 0; _xlim[1] = 0; _ylim[0] = 0; _ylim[1] = 0; #ifdef PLOT_TESTING _plotStartTime = -1; _plotDelay = 0; #endif } MatlabPlot::~MatlabPlot() { _critSect->Enter(); // delete all line objects while (!_line.empty()) { delete *(_line.end() - 1); _line.pop_back(); } delete _critSect; delete _donePlottingEvent; } int MatlabPlot::AddLine(int maxLen /*= -1*/, const char *plotAttrib /*= NULL*/, const char *name /*= NULL*/) { CriticalSectionScoped cs(_critSect); if (!_enabled) { return -1; } MatlabLine *newLine = new MatlabLine(maxLen, plotAttrib, name); _line.push_back(newLine); return (static_cast(_line.size() - 1)); // index of newly inserted line } int MatlabPlot::AddTimeLine(int maxLen /*= -1*/, const char *plotAttrib /*= NULL*/, const char *name /*= NULL*/, int64_t refTimeMs /*= -1*/) { CriticalSectionScoped cs(_critSect); if (!_enabled) { return -1; } MatlabTimeLine *newLine = new MatlabTimeLine(maxLen, plotAttrib, name, refTimeMs); _line.push_back(newLine); return (static_cast(_line.size() - 1)); // index of newly inserted line } int MatlabPlot::GetLineIx(const char *name) { CriticalSectionScoped cs(_critSect); if (!_enabled) { return -1; } // search the list for a matching line name std::vector::iterator it = _line.begin(); bool matchFound = false; int lineIx = 0; for (; it != _line.end(); it++, lineIx++) { if ((*it)->_name == name) { matchFound = true; break; } } if (matchFound) { return (lineIx); } else { return (-1); } } void MatlabPlot::Append(int lineIndex, double x, double y) { CriticalSectionScoped cs(_critSect); if (!_enabled) { return; } // sanity for index if (lineIndex < 0 || lineIndex >= static_cast(_line.size())) { throw "Line index out of range"; exit(1); } return (_line[lineIndex]->Append(x, y)); } void MatlabPlot::Append(int lineIndex, double y) { CriticalSectionScoped cs(_critSect); if (!_enabled) { return; } // sanity for index if (lineIndex < 0 || lineIndex >= static_cast(_line.size())) { throw "Line index out of range"; exit(1); } return (_line[lineIndex]->Append(y)); } int MatlabPlot::Append(const char *name, double x, double y) { CriticalSectionScoped cs(_critSect); if (!_enabled) { return -1; } // search the list for a matching line name int lineIx = GetLineIx(name); if (lineIx < 0) //(!matchFound) { // no match; append new line lineIx = AddLine(-1, NULL, name); } // append data to line Append(lineIx, x, y); return (lineIx); } int MatlabPlot::Append(const char *name, double y) { CriticalSectionScoped cs(_critSect); if (!_enabled) { return -1; } // search the list for a matching line name int lineIx = GetLineIx(name); if (lineIx < 0) //(!matchFound) { // no match; append new line lineIx = AddLine(-1, NULL, name); } // append data to line Append(lineIx, y); return (lineIx); } int MatlabPlot::Length(char *name) { CriticalSectionScoped cs(_critSect); if (!_enabled) { return -1; } int ix = GetLineIx(name); if (ix >= 0) { return (static_cast(_line[ix]->_xData.size())); } else { return (-1); } } void MatlabPlot::SetPlotAttribute(char *name, char *plotAttrib) { CriticalSectionScoped cs(_critSect); if (!_enabled) { return; } int lineIx = GetLineIx(name); if (lineIx >= 0) { _line[lineIx]->SetAttribute(plotAttrib); } } // Must be called under critical section _critSect void MatlabPlot::UpdateData(Engine* ep) { if (!_enabled) { return; } for (std::vector::iterator it = _line.begin(); it != _line.end(); it++) { mxArray* xData = NULL; mxArray* yData = NULL; (*it)->GetPlotData(&xData, &yData); if (xData != NULL) { std::string xName = (*it)->GetXName(); std::string yName = (*it)->GetYName(); _critSect->Leave(); #ifdef MATLAB6 mxSetName(xData, xName.c_str()); mxSetName(yData, yName.c_str()); engPutArray(ep, xData); engPutArray(ep, yData); #else int ret = engPutVariable(ep, xName.c_str(), xData); assert(ret == 0); ret = engPutVariable(ep, yName.c_str(), yData); assert(ret == 0); #endif _critSect->Enter(); } } } bool MatlabPlot::GetPlotCmd(std::ostringstream & cmd, Engine* ep) { _critSect->Enter(); if (!DataAvailable()) { return false; } if (_firstPlot) { GetPlotCmd(cmd); _firstPlot = false; } else { GetRefreshCmd(cmd); } UpdateData(ep); _critSect->Leave(); return true; } // Call inside critsect void MatlabPlot::GetPlotCmd(std::ostringstream & cmd) { // we have something to plot // empty the stream cmd.str(""); // (this seems to be the only way) cmd << "figure; h" << _figHandle << "= plot("; // first line std::vector::iterator it = _line.begin(); cmd << (*it)->GetPlotString(); it++; // remaining lines for (; it != _line.end(); it++) { cmd << ", "; cmd << (*it)->GetPlotString(); } cmd << "); "; if (_legendEnabled) { GetLegendCmd(cmd); } if (_smartAxis) { double xMin = _xlim[0]; double xMax = _xlim[1]; double yMax = _ylim[1]; for (std::vector::iterator it = _line.begin(); it != _line.end(); it++) { xMax = std::max(xMax, (*it)->xMax()); xMin = std::min(xMin, (*it)->xMin()); yMax = std::max(yMax, (*it)->yMax()); yMax = std::max(yMax, fabs((*it)->yMin())); } _xlim[0] = xMin; _xlim[1] = xMax; _ylim[0] = -yMax; _ylim[1] = yMax; cmd << "axis([" << _xlim[0] << ", " << _xlim[1] << ", " << _ylim[0] << ", " << _ylim[1] << "]);"; } int i=1; for (it = _line.begin(); it != _line.end(); i++, it++) { cmd << "set(h" << _figHandle << "(" << i << "), 'Tag', " << (*it)->GetLegendString() << ");"; } } // Call inside critsect void MatlabPlot::GetRefreshCmd(std::ostringstream & cmd) { cmd.str(""); // (this seems to be the only way) std::vector::iterator it = _line.begin(); for (it = _line.begin(); it != _line.end(); it++) { cmd << "h = findobj(0, 'Tag', " << (*it)->GetLegendString() << ");"; cmd << (*it)->GetRefreshString(); } //if (_legendEnabled) //{ // GetLegendCmd(cmd); //} } void MatlabPlot::GetLegendCmd(std::ostringstream & cmd) { std::vector::iterator it = _line.begin(); bool anyLegend = false; for (; it != _line.end(); it++) { anyLegend = anyLegend || (*it)->hasLegend(); } if (anyLegend) { // create the legend cmd << "legend(h" << _figHandle << ",{"; // iterate lines int i = 0; for (std::vector::iterator it = _line.begin(); it != _line.end(); it++) { if (i > 0) { cmd << ", "; } cmd << (*it)->GetLegendString(); i++; } cmd << "}, 2); "; // place legend in upper-left corner } } // Call inside critsect bool MatlabPlot::DataAvailable() { if (!_enabled) { return false; } for (std::vector::iterator it = _line.begin(); it != _line.end(); it++) { (*it)->PurgeOldData(); } return true; } void MatlabPlot::Plot() { CriticalSectionScoped cs(_critSect); _timeToPlot = true; #ifdef PLOT_TESTING _plotStartTime = TickTime::MillisecondTimestamp(); #endif } void MatlabPlot::Reset() { CriticalSectionScoped cs(_critSect); _enabled = true; for (std::vector::iterator it = _line.begin(); it != _line.end(); it++) { (*it)->Reset(); } } void MatlabPlot::SetFigHandle(int handle) { CriticalSectionScoped cs(_critSect); if (handle > 0) _figHandle = handle; } bool MatlabPlot::TimeToPlot() { CriticalSectionScoped cs(_critSect); return _enabled && _timeToPlot; } void MatlabPlot::Plotting() { CriticalSectionScoped cs(_critSect); _plotting = true; } void MatlabPlot::DonePlotting() { CriticalSectionScoped cs(_critSect); _timeToPlot = false; _plotting = false; _donePlottingEvent->Set(); } void MatlabPlot::DisablePlot() { _critSect->Enter(); while (_plotting) { _critSect->Leave(); _donePlottingEvent->Wait(WEBRTC_EVENT_INFINITE); _critSect->Enter(); } _enabled = false; } int MatlabPlot::MakeTrend(const char *sourceName, const char *trendName, double slope, double offset, const char *plotAttrib) { CriticalSectionScoped cs(_critSect); int sourceIx; int trendIx; sourceIx = GetLineIx(sourceName); if (sourceIx < 0) { // could not find source return (-1); } trendIx = GetLineIx(trendName); if (trendIx < 0) { // no trend found; add new line trendIx = AddLine(2 /*maxLen*/, plotAttrib, trendName); } _line[trendIx]->UpdateTrendLine(_line[sourceIx], slope, offset); return (trendIx); } MatlabEngine::MatlabEngine() : _critSect(CriticalSectionWrapper::CreateCriticalSection()), _eventPtr(NULL), _running(false), _numPlots(0) { _eventPtr = EventWrapper::Create(); _plotThread = ThreadWrapper::CreateThread(MatlabEngine::PlotThread, this, kLowPriority, "MatlabPlot"); _running = true; _plotThread->Start(); } MatlabEngine::~MatlabEngine() { _critSect->Enter(); if (_plotThread) { _running = false; _eventPtr->Set(); _plotThread->Stop(); } _plots.clear(); delete _eventPtr; _eventPtr = NULL; _critSect->Leave(); delete _critSect; } MatlabPlot * MatlabEngine::NewPlot(MatlabPlot *newPlot) { CriticalSectionScoped cs(_critSect); //MatlabPlot *newPlot = new MatlabPlot(); if (newPlot) { newPlot->SetFigHandle(++_numPlots); // first plot is number 1 _plots.push_back(newPlot); } return (newPlot); } void MatlabEngine::DeletePlot(MatlabPlot *plot) { CriticalSectionScoped cs(_critSect); if (plot == NULL) { return; } std::vector::iterator it; for (it = _plots.begin(); it < _plots.end(); it++) { if (plot == *it) { break; } } assert (plot == *it); (*it)->DisablePlot(); _plots.erase(it); --_numPlots; delete plot; } bool MatlabEngine::PlotThread(void *obj) { if (!obj) { return (false); } MatlabEngine *eng = (MatlabEngine *) obj; Engine *ep = engOpen(NULL); if (!ep) { throw "Cannot open Matlab engine"; return (false); } engSetVisible(ep, true); engEvalString(ep, "close all;"); while (eng->_running) { eng->_critSect->Enter(); // iterate through all plots for (unsigned int ix = 0; ix < eng->_plots.size(); ix++) { MatlabPlot *plot = eng->_plots[ix]; if (plot->TimeToPlot()) { plot->Plotting(); eng->_critSect->Leave(); std::ostringstream cmd; if (engEvalString(ep, cmd.str().c_str())) { // engine dead return (false); } // empty the stream cmd.str(""); // (this seems to be the only way) if (plot->GetPlotCmd(cmd, ep)) { // things to plot, we have already accessed what we need in the plot plot->DonePlotting(); int64_t start = TickTime::MillisecondTimestamp(); // plot it int ret = engEvalString(ep, cmd.str().c_str()); printf("time=%I64i\n", TickTime::MillisecondTimestamp() - start); if (ret) { // engine dead return (false); } #ifdef PLOT_TESTING if(plot->_plotStartTime >= 0) { plot->_plotDelay = TickTime::MillisecondTimestamp() - plot->_plotStartTime; plot->_plotStartTime = -1; } #endif } eng->_critSect->Enter(); } } eng->_critSect->Leave(); // wait a while eng->_eventPtr->Wait(66); // 33 ms } if (ep) { engClose(ep); ep = NULL; } return (true); } #endif // MATLAB