638 lines
19 KiB
C++
638 lines
19 KiB
C++
/*
|
|
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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#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"<<out;
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
qDebug() << "PD debug| " << out;
|
|
}
|