/* Pure Media Server - A Media Server Sotfware for stage and performing Copyright (C) 2012-2013 Santi NoreƱa libremediaserver@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "libremediaserver.h" #define SOCKET "/tmp/pmspipe" // Pipe wich PD Video sends the files for preview in the GUI #define SOCKET2PD "/tmp/socket" #define CONF_FILE "lms.conf" // File when saving/restoring the configuration on exit/open /////////////////////////////////////////////////////////////////// // Struct for the configuration files /////////////////////////////////////////////////////////////////// struct conf { // Video configuration bool window; quint16 winpositionx; quint16 winpositiony; quint16 winsizex; quint16 winsizey; }; /////////////////////////////////////////////////////////////////// // Constructor /////////////////////////////////////////////////////////////////// libreMediaServer::libreMediaServer(QStringList args, QWidget *parent) : QMainWindow(parent), m_startvideo(0), m_pd_video(NULL), m_gui(FALSE) { qDebug() << "********************************************************************************"; qDebug() << QDate::currentDate() << QTime::currentTime(); // Iniciamos el User Interface ui.setupUi(this); // Parse the command line options if (args.contains("-gui")) { qDebug()<< "libremediaserver Constructor option GUI detected"; m_gui = true; ui.textEdit->appendPlainText("Pure Data GUI's will be shown"); } if (args.contains("-log")) { ui.textEdit->appendPlainText("Log to file"); } //Print the version QString ver; ver = VERSION; ui.textEdit->appendPlainText(ver); ver = COPYRIGHT; ui.textEdit->appendPlainText(ver); ver = LICENSE; ui.textEdit->appendPlainText(ver); qDebug() << VERSION; qDebug() << COPYRIGHT; qDebug() << LICENSE; // Init Unix Local Sockets QFile socket(SOCKET); if (socket.exists()) { socket.remove(); } m_write_video = new QLocalSocket(this); Q_CHECK_PTR(m_write_video); m_server_vid = new QLocalServer(this); Q_CHECK_PTR(m_server_vid); if (!m_server_vid->listen(SOCKET)) { qErrnoWarning("libremediaserver::constructor: Can not listen on unix local server"); } connect(m_server_vid, SIGNAL(newConnection()),this, SLOT(newPeer())); // Iniciamos olad m_ola = new QProcess(this); Q_CHECK_PTR(m_ola); olastart(); // Conectamos los menus connect(ui.actionOpen_conf, SIGNAL(triggered()), this, SLOT(openFile())); connect(ui.actionSave_conf, SIGNAL(triggered()), this, SLOT(saveFile())); connect(ui.actionChange_Media_Path, SIGNAL(triggered()), this, SLOT(ChangeMediaPath())); connect(ui.actionLaunch_OLA_Setup,SIGNAL(triggered()),this, SLOT(olasetup())); // Start PD pdstart(); // Load the configuration open_start(); m_olaInterface = new olaInterface(); Q_CHECK_PTR(m_olaInterface); connect(m_olaInterface->m_msex->m_timer, SIGNAL(timeout()), this, SLOT(sendFrame())); connect(m_olaInterface, SIGNAL(sendDmx(int,int,int)), this, SLOT(receiveDMX(int,int,int))); connect(m_olaInterface, SIGNAL(newFile(QString)), this, SLOT(newFile(QString))); } /////////////////////////////////////////////////////////////////// // Destructor /////////////////////////////////////////////////////////////////// libreMediaServer::~libreMediaServer() { save_finish(); QFile socket(SOCKET); socket.remove(); if (m_server_vid->isListening()) { m_server_vid->close(); delete m_server_vid; } if (m_pd_video != NULL) { disconnect(m_pd_video, SIGNAL(finished(int)), this, SLOT(pdrestart())); m_pd_video->terminate(); if (m_pd_video->waitForFinished(30000)) { delete m_pd_video; } } m_ola->close(); delete m_ola; qDebug() << "PD Video restarts: " << m_startvideo; qDebug() << QDate::currentDate() << QTime::currentTime(); qDebug() << "********************************************************************************"; return; } /////////////////////////////////////////////////////////////////// // open/save the last configuration known /////////////////////////////////////////////////////////////////// // Load the last configuration from the configuration file void libreMediaServer::open_start() { QFile file(CONF_FILE); open(&file); } // Save the last configuration to the conf file void libreMediaServer::save_finish() { QFile file(CONF_FILE); save(&file); } /////////////////////////////////////////////////////////////////// // Menu File /////////////////////////////////////////////////////////////////// // Open a configuration File void libreMediaServer::openFile() { QFileDialog dialog(this); if (!dialog.exec()) return; QStringList fileNames; fileNames = dialog.selectedFiles(); QFile file(fileNames.at(0)); open(&file); } // Save configuration File void libreMediaServer::saveFile() { QFileDialog dialog(this); if (!dialog.exec()) return; QStringList fileNames; fileNames = dialog.selectedFiles(); QFile file(fileNames.at(0)); save(&file); } // Change Media path void libreMediaServer::ChangeMediaPath() { QFileDialog dialog(this); dialog.setFileMode(QFileDialog::Directory); QStringList fileNames; if (!dialog.exec()) return; fileNames = dialog.selectedFiles(); QString file = fileNames.at(0); m_olaInterface->setPath(file); } /////////////////////////////////////////////////////////////////// // Open/save Subroutines. The real work. /////////////////////////////////////////////////////////////////// void libreMediaServer::open(QFile *file) { if (!file->open(QIODevice::ReadOnly | QIODevice::Text)) { qDebug()<<("Can not find the configuration file"); return; } int size = file->size(); unsigned char * fileconf = new unsigned char[size]; memset(fileconf, 0, size); fileconf = file->map(0x00, size); if (fileconf == 0){ qDebug()<<("Cannot map the file"); return; } conf *packet = (conf *)fileconf; // Video configuration ui.window->setChecked(packet->window); ui.winpositionx->setValue(packet->winpositionx); ui.winpositiony->setValue(packet->winpositiony); ui.winsizex->setValue(packet->winsizex); ui.winsizey->setValue(packet->winsizey); file->close(); } void libreMediaServer::save(QFile *file) { if (!file->open(QIODevice::WriteOnly | QIODevice::Text)) { qDebug()<<("Can not open file pms.conf"); return; } int bufferLen = sizeof(struct conf) + m_pathmedia.size(); unsigned char *buffer = new unsigned char[bufferLen]; memset(buffer, 0, bufferLen); conf *packet = (conf *)buffer; // Video Configuration packet->window = ui.window->checkState(); packet->winpositionx = ui.winpositionx->value(); packet->winpositiony = ui.winpositiony->value(); packet->winsizex = ui.winsizex->value(); packet->winsizey = ui.winsizey->value(); int error = file->write((const char *)buffer, bufferLen); QString errorstring = tr("Bytes Write to file %1").arg(error); qDebug()<<"Saved file complete:"<< errorstring; file->close(); delete buffer; } /////////////////////////////////////////////////////////////////// // OLA Stuff /////////////////////////////////////////////////////////////////// void libreMediaServer::olastart() { qDebug()<<("Starting OLA"); m_ola->start("olad", QStringList()<< "-l 3"); // connect(ola, SIGNAL(finished(int)), this, SLOT(olastart())); } void libreMediaServer::olasetup() { QWebView *view = new QWebView(); view->load(QUrl("http://localhost:9090/ola.html")); view->show(); } /////////////////////////////////////////////////////////////////// // Video Controls /////////////////////////////////////////////////////////////////// // Window Configuration void libreMediaServer::on_window_stateChanged(int state) { if ((state == 2)) { QString desc("101 0001;"); if (!sendPacket(desc.toAscii().constData(),desc.size())) { errorsending(); } } if ((state == 0)) { QString desc("101 0000;"); if (!sendPacket(desc.toAscii().constData(),desc.size())) { errorsending(); } } } void libreMediaServer::on_winpositionx_valueChanged() { int x = ui.winpositionx->value(); QString desc = tr("102 %1;").arg(x); if (!sendPacket(desc.toAscii().constData(),desc.size())) { errorsending(); } } void libreMediaServer::on_winpositiony_valueChanged() { int x = ui.winpositiony->value(); QString desc = tr("103 %1;").arg(x); if (!sendPacket(desc.toAscii().constData(),desc.size())) { errorsending(); } } void libreMediaServer::on_winsizex_valueChanged() { int x = ui.winsizex->value(); QString desc = tr("104 %1;").arg(x); if (!sendPacket(desc.toAscii().constData(),desc.size())) { errorsending(); } } void libreMediaServer::on_winsizey_valueChanged() { int x = ui.winsizey->value(); QString desc = tr("105 %1;").arg(x); if (!sendPacket(desc.toAscii().constData(),desc.size())) { errorsending(); } } // Change the Frame Rate void libreMediaServer::on_fpsRate_valueChanged() { QString desc = tr("123 %1;").arg(ui.fpsRate->value()); if (!sendPacket(desc.toAscii().constData(),desc.size())) { errorsending(); } } /////////////////////////////////////////////////////////////////// // Pure Data Video /////////////////////////////////////////////////////////////////// // Start the PD Process, open the ports and connects stdout de Pure Data. void libreMediaServer::pdstart() { m_pd_video = new QProcess(); Q_CHECK_PTR(m_pd_video); // Arrancamos el proceso Pure Data QString program = "./pd"; program.append(" -noaudio -lib Gem -stderr -nostdpath -open lms-video.pd"); if (!m_gui) program.append(" -nogui"); qDebug() << "PD Video starts with arguments: " << program; m_pd_video->setWorkingDirectory("./puredata"); m_pd_video->start(program); if (m_pd_video->waitForStarted(3000)){ ui.textEdit->appendPlainText("Video Engine started."); } else { qWarning("PD Video can not start") ; ui.textEdit->appendPlainText("Video Engine can not start!"); return; } // Connect the output fropm PD Video to stdout slot to process it connect(m_pd_video, SIGNAL(readyReadStandardError()), this, SLOT(stdout())); // Restart PD Video if crash connect(m_pd_video, SIGNAL(finished(int)), this, SLOT(pdrestart())); } // Restart the Pure Data process if crash. Connected with signal finished of QProcess void libreMediaServer::pdrestart() { if (m_pd_video->state()) { return; } save_finish(); qDebug()<<"********************************************************************************"; qDebug()<<"PD Video Restarts:" << ++m_startvideo; ui.textEdit->appendPlainText("PD Video Restarting."); disconnect(m_pd_video, SIGNAL(finished(int)), this, SLOT(pdrestart())); pdstart(); } // New conexion on TCP Server void libreMediaServer::newPeer() { m_read_vid = m_server_vid->nextPendingConnection(); connect(m_read_vid, SIGNAL(readyRead()), this, SLOT(newmessage())); } // New message in a socket stablished connection void libreMediaServer::newmessage() { if (m_read_vid == NULL) { qDebug()<<("Local Socket not created"); newPeer(); return; } QByteArray byteArray; byteArray.resize(m_read_vid->bytesAvailable()); byteArray = m_read_vid->readAll(); if (byteArray.isEmpty()) { return; } QPixmap frame; switch (byteArray.at(0)) { case 11: byteArray.remove(0,2); if (!frame.loadFromData((byteArray))) { qDebug()<<"Layer 1 Convert byte Array to frame failed "; } ui.layer1Preview->setPixmap(frame); break; case 12: byteArray.remove(0,2); if (!frame.loadFromData((byteArray))) { qDebug()<<"Layer 2 Convert byte Array to frame failed "; } ui.layer2Preview->setPixmap(frame); break; case 13: byteArray.remove(0,2); if (!frame.loadFromData((byteArray))) { qDebug()<<"Layer 3 Convert byte Array to frame failed "; break; } ui.layer3Preview->setPixmap(frame); break; case 14: byteArray.remove(0,2); if (!frame.loadFromData(byteArray)) { qDebug()<<"Layer 4 Convert byte Array to frame failed "; break; } ui.layer4Preview->setPixmap(frame); break; case 15: byteArray.remove(0,2); if (!frame.loadFromData(byteArray)) { qDebug()<<"Layer 5 Convert byte Array to frame failed "; break; } ui.layer5Preview->setPixmap(frame); break; case 16: byteArray.remove(0,2); if (!frame.loadFromData(byteArray)) { qDebug()<<"Layer 6 Convert byte Array to frame failed "; break; } ui.layer6Preview->setPixmap(frame); break; case 17: byteArray.remove(0,2); if (!frame.loadFromData(byteArray)) { qDebug()<<"Layer 7 Convert byte Array to frame failed "; break; } ui.layer7Preview->setPixmap(frame); break; case 18: byteArray.remove(0,2); if (!frame.loadFromData(byteArray)) { qDebug()<<"Layer 8 Convert byte Array to frame failed "; break; } ui.layer8Preview->setPixmap(frame); break; default: qDebug()<<"newmessage: Message received but can not identify the cookie"; break; } } // Send the configuration to PD void libreMediaServer::newconexion() { // Init the Unix Socket m_write_video->connectToServer(SOCKET2PD); m_write_video->waitForConnected(3000); if(!(m_write_video->isOpen())) { qErrnoWarning("newconexion: pd write Unix socket not open!"); } // send the windows info on_winpositionx_valueChanged(); on_winpositiony_valueChanged(); on_winsizex_valueChanged(); on_winsizey_valueChanged(); on_window_stateChanged(ui.window->checkState()); } // Send packet to Pure Data video bool libreMediaServer::sendPacket(const char *buffer, int bufferLen) { if (QLocalSocket::ConnectedState != m_write_video->state()) { // qErrnoWarning("sendPacket:Socket not conected: "); return false; } if (bufferLen != m_write_video->write((const char*)buffer, bufferLen)) { qErrnoWarning("sendPacket:Can not write to socket: "); return false; } return true; } // Send DMX Channel to Pure Data void libreMediaServer::receiveDMX(int layer, int channel, int value) { QString buffer = tr("%1 %2 %3;").arg(layer).arg(channel).arg(value); if (!sendPacket(buffer.toAscii().constData(), buffer.size())) { errorsending(); } } // Request PD to open a new media File bool libreMediaServer::newFile(QString file) { if (QLocalSocket::ConnectedState != m_write_video->state()) { qErrnoWarning("sendPacket:Socket not conected: "); return false; } if (file.size() != m_write_video->write(file.toAscii().constData(), file.size())) { qErrnoWarning("sendPacket:Can not write to socket: "); return false; } return true; } // Function error sending packets to PD video void libreMediaServer::errorsending() { qErrnoWarning("errorsending: Can not talk to Pure Data!"); ui.textEdit->appendPlainText("errorsending: Can not send packets to PD"); } /////////////////////////////////////////////////////////////////// // Previews /////////////////////////////////////////////////////////////////// // Master prevuew for CITP/MSEx 1.0 and GUI void libreMediaServer::sendFrame() { QPixmap frame = QPixmap::grabWindow(QApplication::desktop()->winId(), ui.winpositionx->value() , ui.winpositiony->value(),ui.winsizex->value(),ui.winsizey->value()); if (!frame) { qDebug()<<"sendFrame: Can not take frame"; return; } ui.masterPreview->setPixmap(frame); QImage image = QImage(frame.toImage()); if (!image.byteCount()) { qDebug()<<"sendFrame: Can not convert screen capture to image"; return; } image = image.scaledToWidth(88); image = image.convertToFormat(QImage::Format_RGB888); m_olaInterface->m_msex->sendFrame(image); } // Enable/Disable the GUI Master preview void libreMediaServer::on_previewMaster_stateChanged (int state) { if ((state == 0)) { m_olaInterface->m_msex->m_timer->stop(); } if ((state == 2)) { m_olaInterface->m_msex->m_timer->start(); } } // Enable/Disable the GUI Layers preview void libreMediaServer::on_previewLayer_stateChanged (int state) { if ((state == 0)) { QString desc = tr("122 0;"); if (!sendPacket(desc.toAscii().constData(),desc.size())) { errorsending(); } } if ((state == 2)) { QString desc = tr("122 1;"); if (!sendPacket(desc.toAscii().constData(),desc.size())) { errorsending(); } } } /////////////////////////////////////////////////////////////////// // Terminal and Log //////////////////////////////////////////////////////////////////// // Sacamos la salida de Pure Data Video en la terminal. Filtra mensajes para el GUI. void libreMediaServer::stdout() { QByteArray out = m_pd_video->readAllStandardError(); if (out.size() < 7) {return;} out.chop(1); int j = out.indexOf("togui: ",0); if ((j >= 0) && (out.size() > (j+7))) { // int i = m_pathmedia.size() + 16; out.remove(0,j); switch (out.at(7)) { case '0': qDebug()<<"Loadbang Video"; ui.textEdit->appendPlainText("LoadBang Video received."); newconexion(); break; case '9': out.remove(0,9); ui.fpsCounter->display(out.toInt()); break; default: qDebug()<<"stdout:Invalid cookie received"<