From 893bec26cd1bc83fd85bb7dfdfd6225d9f6ee5b5 Mon Sep 17 00:00:00 2001 From: Chris Xiong Date: Fri, 1 May 2020 01:31:45 +0800 Subject: Visualization renderer now understands command line arguments. Fixed wrong tick calculation in render mode. Fixed most of the hangs and crashes associated with the renderer. Do not write pixel data in the visualization thread. --- visualization/SMELT | 2 +- visualization/qmpvisualization.cpp | 27 +++--- visualization/qmpvisualization.hpp | 6 +- visualization/renderer/main.cpp | 21 ++++- visualization/renderer/qmpsettingsro.cpp | 59 +++++++++++++ visualization/renderer/qmpsettingsro.hpp | 1 + visualization/renderer/qmpvisrendercore.cpp | 126 ++++++++++++++++++++++------ visualization/renderer/qmpvisrendercore.hpp | 15 +++- 8 files changed, 208 insertions(+), 49 deletions(-) diff --git a/visualization/SMELT b/visualization/SMELT index 87a25aa..b83fb43 160000 --- a/visualization/SMELT +++ b/visualization/SMELT @@ -1 +1 @@ -Subproject commit 87a25aa8fd3cb5a67747bff63711338e0f88b7c3 +Subproject commit b83fb431fdff9329dc3cf9457f1934b6156eedfa diff --git a/visualization/qmpvisualization.cpp b/visualization/qmpvisualization.cpp index e4e4428..d63b1e3 100644 --- a/visualization/qmpvisualization.cpp +++ b/visualization/qmpvisualization.cpp @@ -208,7 +208,7 @@ void qmpVisualization::reset() } } -void qmpVisualization::switchToRenderMode(void(*frameCallback)(void*,size_t),bool _hidewindow) +void qmpVisualization::switchToRenderMode(void(*frameCallback)(void*,size_t,uint32_t,uint32_t),bool _hidewindow) { rendermode=true; framecb=frameCallback; @@ -709,23 +709,26 @@ bool qmpVisualization::update() } } if(osdpos==4){sm->smRenderEnd();return shouldclose;} + uint32_t ltpc=~0u; for(uint32_t i=elb;itcs>ctk)break; if(pool[i]->ch==998)cts=pool[i]->key; if(pool[i]->ch==997)cks=pool[i]->key; if(pool[i]->ch==996) - { - ctp=pool[i]->key; - if(rendermode) - { - lstk=ctk; - cfr=1; - } - } + ltpc=i; if(pool[i]->ch==995)cpbr[pool[i]->vel]=pool[i]->key; if(pool[i]->ch==994)cpw[pool[i]->vel]=pool[i]->key; } + if(~ltpc&&ctp!=pool[ltpc]->key) + { + ctp=pool[ltpc]->key; + if(rendermode) + { + lstk=pool[ltpc]->tcs; + cfr=1; + } + } int t,r;t=cks;r=(int8_t)((t>>8)&0xFF)+7;t&=0xFF; std::wstring ts(t?minors:majors,2*r,2); int step=int(1.33*fontsize); @@ -777,11 +780,11 @@ bool qmpVisualization::update() if(rendermode) { if(ctk>api->getMaxTick()) - framecb(nullptr,0); + framecb(nullptr,0,ctk,api->getMaxTick()); else { sm->smPixelCopy(0,0,wwidth,wheight,4*wwidth*wheight,fbcont); - framecb(fbcont,4*wwidth*wheight); + framecb(fbcont,4*wwidth*wheight,ctk,api->getMaxTick()); } } return shouldclose; @@ -813,6 +816,7 @@ qmpVisualization::qmpVisualization(qmpPluginAPI* _api) api=_api; inst=this; rendermode=false; + hidewindow=false; } qmpVisualization::~qmpVisualization() { @@ -824,7 +828,6 @@ void qmpVisualization::init() h=new CMidiVisualHandler(this); closeh=new CloseHandler(this); rendererTh=nullptr;playing=false; - hidewindow=false; memset(rpnid,0xFF,sizeof(rpnid)); memset(rpnval,0xFF,sizeof(rpnval)); memset(spectra,0,sizeof(spectra)); diff --git a/visualization/qmpvisualization.hpp b/visualization/qmpvisualization.hpp index 824093a..8a8d832 100644 --- a/visualization/qmpvisualization.hpp +++ b/visualization/qmpvisualization.hpp @@ -46,7 +46,7 @@ class qmpVisualization:public qmpPluginIntf,public qmpFuncBaseIntf bool rendermode,hidewindow; int herh,heh,hfrf; int uihb,uihs,uihp,uihr,uihk; - void(*framecb)(void*,size_t); + void(*framecb)(void*,size_t,uint32_t,uint32_t); DWORD* fbcont; std::vector>tspool; int traveld[16][128];bool notestatus[16][128],lastnotestatus[16][128]; @@ -69,7 +69,7 @@ class qmpVisualization:public qmpPluginIntf,public qmpFuncBaseIntf void stop(); void pause(); void reset(); - void switchToRenderMode(void(*frameCallback)(void*,size_t),bool _hidewindow); + void switchToRenderMode(void(*frameCallback)(void*,size_t,uint32_t,uint32_t),bool _hidewindow); void init(); void deinit(); @@ -109,7 +109,7 @@ extern "C"{ {return new qmpVisualization(api);} EXPORTSYM const char* qmpPluginGetAPIRev() {return QMP_PLUGIN_API_REV;} - EXPORTSYM void switchToRenderMode(void(*frameCallback)(void*,size_t),bool hidewindow) + EXPORTSYM void switchToRenderMode(void(*frameCallback)(void*,size_t,uint32_t,uint32_t),bool hidewindow) { if(qmpVisualization::instance()) qmpVisualization::instance()->switchToRenderMode(frameCallback,hidewindow); diff --git a/visualization/renderer/main.cpp b/visualization/renderer/main.cpp index ec4dd1d..cca8a12 100644 --- a/visualization/renderer/main.cpp +++ b/visualization/renderer/main.cpp @@ -2,17 +2,32 @@ #include #include "qmpvisrendercore.hpp" +#include "qmpsettingsro.hpp" int main(int argc,char **argv) { QCoreApplication::setApplicationName("qmpvisrender"); + QCoreApplication::setApplicationVersion("0.0.0"); QCoreApplication a(argc,argv); QCommandLineParser clp; clp.setApplicationDescription("Renderer a visualization of a midi file."); clp.addHelpOption(); - clp.parse(a.arguments()); - qmpVisRenderCore core; - core.loadVisualizationLibrary(); + clp.addVersionOption(); + clp.addOption({{"f","output-file"},"File name of the output file.","filename","output.mp4"}); + clp.addOption({"ffmpeg-args","Custom output option arguments for ffmpeg.","args","-vf vflip -pix_fmt yuv420p -c:v libx264 -preset slow -crf 22"}); + clp.addOption({"ffmpeg-pre-args","Custom arguments passed to ffmpeg before input arguments.","args",""}); + clp.addOption({"ffmpeg-exec","Specify the path to the ffmpeg executable.","exec","ffmpeg"}); + clp.addOption({{"s","show-window"},"Do not hide the visualization window."}); + clp.addOption({{"c","config"},"Load options from the configuration file.","qmprc file"}); + clp.addOption({{"o","option"},"Set option for the visualization module.","key-value pair"}); + clp.addPositionalArgument("file","MIDI file to render"); + clp.process(a.arguments()); + qmpVisRenderCore core(&clp); + if(clp.positionalArguments().empty()) + clp.showHelp(1); + if(!core.loadVisualizationLibrary()) + return 1; + core.loadSettings(); if(clp.positionalArguments().size()) core.setMIDIFile(clp.positionalArguments().front().toStdString().c_str()); core.startRender(); diff --git a/visualization/renderer/qmpsettingsro.cpp b/visualization/renderer/qmpsettingsro.cpp index cc6e0bf..a66943b 100644 --- a/visualization/renderer/qmpsettingsro.cpp +++ b/visualization/renderer/qmpsettingsro.cpp @@ -165,3 +165,62 @@ void qmpSettingsRO::load(const char *path) settings.insert(k,qsettings->value(k)); } } + +void qmpSettingsRO::setopt(std::string key, std::string val) +{ + if(options.find(key)==options.end()) + { + std::string nkey="Visualization/"+key; + if(options.find(nkey)==options.end()) + { + qDebug("invalid option key %s",key.c_str()); + return; + } + else key=nkey; + } + char *rptr; + switch(options[key].type) + { + case qmpOptionR::ParameterType::parameter_int: + { + long long v=strtoll(val.c_str(),&rptr,10); + if(rptr==val.c_str()||v>INT_MAX||v(v)); + } + break; + case qmpOptionR::ParameterType::parameter_uint: + { + long long v=strtoll(val.c_str(),&rptr,10); + if(rptr==val.c_str()||v>UINT32_MAX||v<0) + qDebug("invalid value for option %s",key.c_str()); + setOptionUint(key,static_cast(v)); + } + break; + case qmpOptionR::ParameterType::parameter_double: + { + errno=0; + double v=strtod(val.c_str(),&rptr); + if(rptr==val.c_str()||errno) + qDebug("invalid value for option %s",key.c_str()); + setOptionDouble(key,v); + } + break; + case qmpOptionR::ParameterType::parameter_bool: + { + if(val!="true"&&val!="false") + qDebug("invalid value for option %s",key.c_str()); + setOptionBool(key,val=="true"); + } + break; + case qmpOptionR::ParameterType::parameter_str: + case qmpOptionR::ParameterType::parameter_url: + setOptionString(key,val); + break; + case qmpOptionR::ParameterType::parameter_enum: + setOptionEnumIntOptName(key,val); + break; + default: + break; + } +} diff --git a/visualization/renderer/qmpsettingsro.hpp b/visualization/renderer/qmpsettingsro.hpp index 30ab9b6..5c5361d 100644 --- a/visualization/renderer/qmpsettingsro.hpp +++ b/visualization/renderer/qmpsettingsro.hpp @@ -64,6 +64,7 @@ public: void setOptionEnumIntOptName(std::string key,std::string valname); void load(const char* path); + void setopt(std::string key,std::string val); private: std::map options; std::vector optionlist; diff --git a/visualization/renderer/qmpvisrendercore.cpp b/visualization/renderer/qmpvisrendercore.cpp index fb1a7ef..a2733a7 100644 --- a/visualization/renderer/qmpvisrendercore.cpp +++ b/visualization/renderer/qmpvisrendercore.cpp @@ -8,11 +8,12 @@ #include #include +#include #include #include qmpVisRenderCore *qmpVisRenderCore::inst=nullptr; -qmpVisRenderCore::qmpVisRenderCore():QObject(nullptr) +qmpVisRenderCore::qmpVisRenderCore(QCommandLineParser *_clp):QObject(nullptr),clp(_clp) { inst=this; player=new CMidiPlayer(); @@ -21,17 +22,21 @@ qmpVisRenderCore::qmpVisRenderCore():QObject(nullptr) msettings->registerOptionEnumInt("MIDI","Text encoding","Midi/TextEncoding",{"Unicode","Big5","Big5-HKSCS","CP949","EUC-JP","EUC-KR","GB18030","KOI8-R","KOI8-U","Macintosh","Shift-JIS"},0); } -void qmpVisRenderCore::loadVisualizationLibrary() +bool qmpVisRenderCore::loadVisualizationLibrary() { mp=dlopen("libvisualization.so",RTLD_LAZY); - if(!mp)fprintf(stderr,"failed to load visualization module!\n"); + if(!mp) + { + fprintf(stderr,"failed to load the visualization module!\n"); + return false; + } GetInterface_func getintf=reinterpret_cast(dlsym(mp,"qmpPluginGetInterface")); SwitchMode_func switchmode=reinterpret_cast(dlsym(mp,"switchToRenderMode")); vf=nullptr; vp=getintf(api); - switchmode(&qmpVisRenderCore::framefunc,false); + switchmode(&qmpVisRenderCore::framefunc,!clp->isSet("show-window")); vp->init(); - msettings->load("/home/chrisoft/.config/qmprc"); + return true; } void qmpVisRenderCore::unloadVisualizationLibrary() @@ -40,6 +45,21 @@ void qmpVisRenderCore::unloadVisualizationLibrary() dlclose(mp); } +void qmpVisRenderCore::loadSettings() +{ + if(clp->isSet("config")) + msettings->load(clp->value("config").toStdString().c_str()); + for(auto &o:clp->values("option")) + { + int sp=o.indexOf('='); + if(!~sp) + qDebug("invalid option pair: %s",o.toStdString().c_str()); + QString key=o.left(sp); + QString value=o.mid(sp+1); + msettings->setopt(key.toStdString(),value.toStdString()); + } +} + void qmpVisRenderCore::setMIDIFile(const char *url) { player->playerLoadFile(url); @@ -51,38 +71,92 @@ void qmpVisRenderCore::startRender() ffmpegproc=new QProcess(); ffmpegproc->setProgram("ffmpeg"); QStringList arguments; + arguments.append(split_arguments(clp->value("ffmpeg-pre-args"))); arguments <<"-f"<<"rawvideo" <<"-pixel_format"<<"rgba" - <<"-video_size"<<"1600x900" - <<"-framerate"<<"60" + <<"-video_size"<getOptionInt("Visualization/wwidth")).arg(msettings->getOptionInt("Visualization/wheight")) + <<"-framerate"<getOptionInt("Visualization/tfps")) <<"-i"<<"pipe:"; - arguments - <<"-vf"<<"vflip" - <<"-pix_fmt"<<"yuv420p" - <<"-c:v"<<"libx264" - <<"-preset"<<"fast" - <<"-crf"<<"22"; - arguments<<"output.mp4"; + arguments.append(split_arguments(clp->value("ffmpeg-args"))); + arguments<value("output-file"); ffmpegproc->setArguments(arguments); - ffmpegproc->start(); + QMetaObject::Connection frameconn=connect(this,&qmpVisRenderCore::frameRendered,this, + [this,&frameconn](void* px,size_t sz,uint32_t c,uint32_t t) + { + if(sz) + { + if(!ffmpegproc->isOpen())return; + ffmpegproc->write(static_cast(px),static_cast(sz)); + while(ffmpegproc->bytesToWrite()>1<<26) + ffmpegproc->waitForBytesWritten(); + } + fprintf(stderr,"Rendered tick %u of %u, %.2f%% done.\r",c,t,100.*c/t); + if(c>t) + { + this->ffmpegproc->closeWriteChannel(); + disconnect(frameconn); + qApp->exit(0); + } + },Qt::ConnectionType::BlockingQueuedConnection); connect(ffmpegproc,QOverload::of(&QProcess::finished), - [this](int x,QProcess::ExitStatus){qDebug("%d",x);qDebug()<ffmpegproc->readAllStandardError();}); - vf->show(); - startcb(nullptr,nullptr); + [this,&frameconn](int x,QProcess::ExitStatus st){ + qDebug("%s",this->ffmpegproc->readAllStandardError().data()); + disconnect(frameconn); + if(x||st==QProcess::ExitStatus::CrashExit) + qApp->exit(1); + else + qApp->exit(0); + }); + QMetaObject::invokeMethod(this,[this](){ + ffmpegproc->start(); + vf->show(); + startcb(nullptr,nullptr); + },Qt::ConnectionType::QueuedConnection); } -void qmpVisRenderCore::framefunc(void *px, size_t sz) +QStringList qmpVisRenderCore::split_arguments(QString a) { - if(sz) + QStringList ret; + QString buf; + bool escaped=false; + for(int i=0;iffmpegproc->write((const char*)px,sz); - while(inst->ffmpegproc->bytesToWrite()>1<<26) + if(a[i]=='\\') + { + if(escaped) + { + buf+='\\'; + escaped=false; + } + else escaped=true; + } + else if(a[i]==' ') { - inst->ffmpegproc->waitForBytesWritten(); - QThread::yieldCurrentThread(); + if(escaped)buf+=' '; + else + { + ret.append(buf); + buf.clear(); + } + escaped=false; + } + else + { + if(escaped) + { + buf+='\\'; + escaped=false; + } + buf+=a[i]; } } - else - inst->ffmpegproc->closeWriteChannel(); + if(buf.length()) + ret.append(buf); + return ret; +} + +void qmpVisRenderCore::framefunc(void *px, size_t sz,uint32_t curf,uint32_t totf) +{ + emit inst->frameRendered(px,sz,curf,totf); } diff --git a/visualization/renderer/qmpvisrendercore.hpp b/visualization/renderer/qmpvisrendercore.hpp index 0c024f1..27337ba 100644 --- a/visualization/renderer/qmpvisrendercore.hpp +++ b/visualization/renderer/qmpvisrendercore.hpp @@ -9,6 +9,7 @@ class qmpPluginAPIStub; class CMidiPlayer; class qmpSettingsRO; +class QCommandLineParser; class QProcess; @@ -16,14 +17,18 @@ class qmpVisRenderCore:public QObject { Q_OBJECT public: - qmpVisRenderCore(); - void loadVisualizationLibrary(); + qmpVisRenderCore(QCommandLineParser *_clp); + bool loadVisualizationLibrary(); void unloadVisualizationLibrary(); + void loadSettings(); void setMIDIFile(const char* url); void startRender(); qmpSettingsRO* settings(){return msettings;} +signals: + void frameRendered(void* px,size_t sz,uint32_t current_tick,uint32_t total_ticks); + private: qmpPluginIntf *vp; qmpFuncBaseIntf *vf; @@ -33,11 +38,13 @@ private: CMidiPlayer *player; qmpSettingsRO *msettings; QProcess *ffmpegproc; + QCommandLineParser *clp; + QStringList split_arguments(QString a); typedef qmpPluginIntf*(*GetInterface_func)(qmpPluginAPI*); - typedef void(*SwitchMode_func)(void(*frameCallback)(void*,size_t),bool hidewindow); + typedef void(*SwitchMode_func)(void(*frameCallback)(void*,size_t,uint32_t,uint32_t),bool hidewindow); friend class qmpPluginAPIStub; - static void framefunc(void* px,size_t sz); + static void framefunc(void* px, size_t sz, uint32_t curf, uint32_t totf); static qmpVisRenderCore *inst; }; -- cgit v1.2.3