PARP Research Group University of Murcia, Spain


src/qvio/qvmplayerreader.cpp

Go to the documentation of this file.
00001  /*
00002  *      Copyright (C) 2007, 2008, 2009. PARP Research Group.
00003  *      <http://perception.inf.um.es>
00004  *      University of Murcia, Spain.
00005  *
00006  *      This file is part of the QVision library.
00007  *
00008  *      QVision is free software: you can redistribute it and/or modify
00009  *      it under the terms of the GNU Lesser General Public License as
00010  *      published by the Free Software Foundation, version 3 of the License.
00011  *
00012  *      QVision is distributed in the hope that it will be useful,
00013  *      but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  *      GNU Lesser General Public License for more details.
00016  *
00017  *      You should have received a copy of the GNU Lesser General Public
00018  *      License along with QVision. If not, see <http://www.gnu.org/licenses/>.
00019  */
00020 
00024 
00025 #include <sys/types.h>
00026 #include <sys/stat.h>
00027 #include <unistd.h>
00028 #include <stdlib.h>
00029 #include <fcntl.h>
00030 #include <stdio.h>
00031 
00032 #include <iostream>
00033 
00034 #include <QDebug>
00035 #include <QStringList>
00036 #include <QRegExp>
00037 
00038 #include <qvipp.h>
00039 #include <qvio.h>
00040 
00041 #include <QVMPlayerReader>
00042 
00043 #include <QTimer>
00044 
00045 /******************* Auxiliary QVCheckOKMPlayer class *******************/
00046 
00047 QVCheckOKMPlayer::QVCheckOKMPlayer(QFile & fifo_file,int max_time_ms_to_wait_for_open) : QThread(), _fifo_file(fifo_file), _max_time_ms_to_wait_for_open(max_time_ms_to_wait_for_open)
00048         {
00049         qDebug() << "QVCheckOKMPlayer::QVCheckOKMPlayer() <- starting thread";
00050         moveToThread(this);
00051         start();
00052         }
00053 
00054 void QVCheckOKMPlayer::run()
00055         {
00056         QTimer::singleShot(_max_time_ms_to_wait_for_open, this, SLOT(writeErrorInFifo()));
00057         qDebug() << "QVCheckOKMPlayer::run() <- entering event loop";
00058         exec();
00059         qDebug() << "QVCheckOKMPlayer::run() <- back from event loop";
00060         }
00061 
00062 void QVCheckOKMPlayer::writeErrorInFifo()
00063         {
00064         qDebug() << "QVCheckOKMPlayerCamera::writeErrorInFifo()";
00065         _fifo_file.write("MPLAYER ERROR\n"); 
00066         qDebug() << "QVCheckOKMPlayerCamera::writeErrorInFifo() -> return";
00067         };
00068 
00069 /***************************** QVMPlayerReader class **************************/
00070 
00071 void QVMPlayerReader::initMPlayerArgs(QString urlString, unsigned int suggested_cols, unsigned int suggested_rows)
00072         {
00073         qDebug() << "QVMPlayerReader::initMPlayerArgs(" << urlString << "," << suggested_cols << "," << suggested_rows << ")";
00074 
00075         mplayer_args = QStringList();
00076 
00077         // Loop option and fixed-vo must go first:
00078         if(not (open_options & NoLoop)) mplayer_args << "-loop" << "0";
00079 
00080         mplayer_args << "-fixed-vo";
00081 
00082         QUrl url(urlString);
00083 
00084         path = QString();
00085 
00086         if (url.host() != "")
00087                 path = url.host() + "/";
00088 
00089         path += url.path();
00090 
00091         // Here we infer the type of the video source from the url.
00092         // If it is not specified in the schema of it, certain match rules are
00093         // applied to guess it out.
00094         if (url.scheme() != "")                         // schema specified by the user
00095                 schema = url.scheme();
00096         else if (urlString.startsWith("/dev/video"))    // a v4l device
00097                 schema = "v4l";
00098         else if (urlString.startsWith("/dev/dv"))       // a dv device
00099                 schema = "dv";
00100         else if (urlString.contains("*"))               // a multifile
00101                 schema = "mf";
00102         else if (urlString.startsWith("www."))          // a http file
00103                 schema = "http";
00104         else if (urlString.startsWith("ftp."))          // a ftp file
00105                 schema = "ftp";
00106         else
00107                 schema = QString();
00108 
00109         live_camera = TRUE;     // Default configuration; later we set it to FALSE if it applies.
00110 
00111         // Different mplayer args for different kind of image sources:
00112         if ((schema == "v4l") or (schema == "v4l2") or (schema == "analog"))
00113                 {
00114                 // Video for Linux cameras:
00115                 QString urlQueryValues = QString("driver=%1:device=%2").arg(schema).arg(path);
00116 
00117                 QList<QPair<QString, QString> > queryItems = url.queryItems();
00118                 for (int i = 0; i < queryItems.size(); ++i)
00119                         urlQueryValues += ":" + queryItems.at(i).first + "=" + queryItems.at(i).second;
00120 
00121                 mplayer_args << "tv://" << "-tv" << urlQueryValues;
00122                 }
00123         else if (schema == "dv")
00124                 // DV cameras (connected through firewire):
00125                 mplayer_args << path << "-demuxer" << "rawdv" << "-cache" << "400";
00126         else if (schema == "iidc")
00127                 // IIDC cameras (connected through firewire):
00128                 // For example, iidc:///dev/video1394/0?from=/dev/video0:to=/dev/video1
00129                 qFatal("Currently this driver does not work (apparently with\n"
00130                         "vloopback writing and then reading from a fifo with mplayer\ndoes not work).\n");
00131         else if (schema == "tv")
00132                 // Analog TV input:
00133                 qFatal("tv URL: Still not implemented\n");
00134         else if (schema == "dvb")
00135                 // Digital TV input:
00136                 qFatal("dvb URL: Still not implemented\n");
00137         else    // Format can be rtsp:// http://, ftp://, dvd://, vcd://, mf:// or file://
00138                 { 
00139                 //  We pass the url it directly to mplayer:
00140                 live_camera = FALSE;
00141                 if (schema != "")
00142                         mplayer_args << QString(schema + "://" + path);
00143                 else
00144                         mplayer_args << path;
00145                 }
00146 
00147         // IMPORTANT!! All -vf filters MUST be together, separated by commas, and in
00148         // the right order. By now, we only use deinterlacing and scale, and in that order:
00149         QString aux;
00150 
00151         // Deinterlace option:
00152         if(open_options & Deinterlaced) aux = "pp=md";
00153 
00154         // Scaling:
00155         if(suggested_cols != 0 and suggested_rows != 0)
00156                 {
00157                 if(aux != QString())
00158                         aux += QString(",scale=%1:%2").arg(suggested_cols).arg(suggested_rows);
00159                 else
00160                         aux = QString("scale=%1:%2").arg(suggested_cols).arg(suggested_rows);
00161                 } 
00162         if (aux != QString()) mplayer_args << "-vf" << aux;
00163 
00164         // Real time option:
00165         if(not (open_options & RealTime))
00166                 {
00167                 if(not live_camera)
00168                         mplayer_args << "-benchmark";
00169                 }
00170 
00171         // Additional arguments (slave, quiet, nosound & yuv4mpeg output):
00172         mplayer_args << "-slave" << "-quiet" << "-nosound" << "-vo" << QString("yuv4mpeg:file=%1").arg(namedPipe->getInputFilePath());
00173 
00174         qDebug() << "QVMPlayerReader::initMPlayerArgs(): MPlayer args = " << mplayer_args;
00175         qDebug() << "QVMPlayerReader::initMPlayerArgs() <- return";
00176         }
00177 
00178 int QVMPlayerReader::interpretMPlayerOutput()
00179         {
00180         int length = -1;
00181         char buf[1024];
00182 
00183         length = mplayer->readLine(buf, sizeof(buf));
00184 
00185         if (length == -1)
00186                 qDebug() << "QVMPlayerReader::interpretMPlayerOutput(): length == -1";
00187         else    {
00188                 QString str(buf);
00189                 QStringList variables = str.simplified().split("=");
00190                 QStringList palabras = str.simplified().split(" ");
00191 
00192                 if(variables[0] == "ANS_LENGTH")
00193                         {
00194                         time_length = variables[1].toDouble();
00195                         qDebug() << "QVMPlayerReader::interpretMPlayerOutput(): updating time_length =" << time_length;
00196                         }
00197                 else if(variables[0] == "ANS_TIME_POSITION")
00198                         {
00199                         time_pos = variables[1].toDouble();
00200                         qDebug() << "QVMPlayerReader::interpretMPlayerOutput(): updating time_pos =" << time_pos;
00201                         }
00202                 else
00203                         qDebug() << "QVMPlayerReader::interpretMPlayerOutput(): uninterpreted mplayer output:" << str;
00204                 }
00205         qDebug() << "QVMPlayerReader::interpretMPlayerOutput() <- return " << length;
00206         return length;
00207         }
00208 
00209 bool QVMPlayerReader::performGrab()
00210         {
00211         qDebug() << "QVMPlayerReader::performGrab()";
00212 
00213         if (not camera_opened)
00214                 {
00215                 qDebug() << "QVMPlayerReader::performGrab() returns FALSE (camera is closed)";
00216                 return false;
00217                 }
00218 
00219         qDebug() << "QVMPlayerReader::performGrab(): sending command get_time_pos to mplayer";
00220         mplayer->write("pausing_keep get_time_pos\n"); // pausing_keep in fact not needed...
00221 
00222         // New frame read:
00223         qDebug() << "QVMPlayerReader::performGrab: reading YUV frame: readYUV4MPEG2Frame()";
00224         if (!readYUV4MPEG2Frame(fifoInput, imgY, imgU, imgV))
00225                 {
00226                 qDebug() << "QVMPlayerReader::performGrab: No more frames left, closing camera";
00227                 end_of_video = TRUE;
00228                 closeCam();
00229                 qDebug() << "QVMPlayerReader::performGrab: No more frames left, camera closed, returning false";
00230                 return FALSE;
00231                 }
00232 
00233         frames_grabbed++;
00234         qDebug() << "QVMPlayerReader::performGrab: new frame read (" << frames_grabbed << ")";
00235 
00236         // Interpret mplayer output while it is writing something:
00237         qDebug() << "QVMPlayerReader::performGrab: now interpreting mplayer output";
00238         while(interpretMPlayerOutput() > 0);
00239 
00240         qDebug() << "QVMPlayerReader::performGrab: emitting newGrab() signal";
00241         emit newGrab();
00242 
00243         qDebug() << "QVMPlayerReader::performGrab() <- returning TRUE";
00244         return TRUE;
00245         }
00246 
00247 static inline int iRoundUp(int a, int b) {
00248   return (a % b == 0) ? a : b*(a / b + 1) ;
00249 }
00250 
00251 bool QVMPlayerReader::openCam(const QString & urlstring, OpenOptions opts, unsigned int suggested_cols, unsigned int suggested_rows)
00252         {
00253         qDebug() << "QVMPlayerReader::openCam(" << qPrintable(urlstring) << "," << static_cast<int>(opts) << ","
00254                 << suggested_cols << "," << suggested_rows << "," << opts << ")";
00255 
00256         if (camera_opened)
00257                 {
00258                 qDebug() << "QVMPlayerReader::openCam() <- closing previously opened camera";
00259                 closeCam();
00260                 qDebug() << "QVMPlayerReader::openCam() <- previously opened camera closed";
00261                 }
00262 
00263         open_options = opts;
00264 
00265         // Pipe initialization
00266         namedPipe = new QNamedPipe(QString("mplayer"));
00267         qDebug() << "QVMPlayerReader::openCam(): Named pipe created" << namedPipe->getOutputFilePath();
00268 
00269         // Launching MPlayer:
00270         mplayer = new QProcess;
00271         mplayer->setParent(this);                               // set the mplayerreader's parent = this, and move it to his thread.
00272         mplayer->moveToThread(this->thread());  // For QVMPlayerReader can send it signals, like waitForFinished in closeCam
00273         initMPlayerArgs(urlstring, suggested_cols, suggested_rows);
00274         mplayer->start(MPLAYER_BINARY_PATH, mplayer_args);
00275         qDebug() << "QVMPlayerReader::openCam(): after mplayer->start()";
00276         if(not mplayer->waitForStarted(1000))
00277                 qFatal("Mplayer failed to start in a second: Are you sure it is installed and in the correct PATH?");
00278         qDebug() << "QVMPlayerReader::openCam(): after mplayer->waitForstarted()";
00279         
00280         // Important to open fifo now in ReadWrite mode, to avoid infinite waiting if mplayer did
00281         // not start OK, and because perhaps the former guarding thread will write in the FIFO:
00282         qDebug() << "QVMPlayerReader::openCam(): opening fifo " << qPrintable(namedPipe->getOutputFilePath());
00283         fifoInput.setFileName(namedPipe->getOutputFilePath());
00284         if(fifoInput.open(QIODevice::ReadWrite|QIODevice::Unbuffered) == -1)
00285                 qFatal("Error opening fifo %s\n", qPrintable(namedPipe->getOutputFilePath()));
00286         qDebug() << "QVMPlayerReader::openCam(): fifo opened";
00287 
00288         // We create a guarding thread that will unblock the subsequent readYUV4MPEG2Header in the
00289         // case that mplayer does not start OK (due, for example, to a wrong URL, an incorrect
00290         // file format, or whatever). If mplayer starts OK before two seconds (for local videos) or
00291         // ten seconds (for DVD, VCD and remote videos), this thread silently stops and is deleted.
00292         const uInt waitMilisecs = (     schema == "http" or schema == "ftp" or
00293                                         schema == "rtsp" or schema == "dvd" or
00294                                         schema == "vcd" )? 10000:2000;
00295         QVCheckOKMPlayer check_thread(fifoInput, waitMilisecs);
00296 
00297         qDebug()<< "QVMPlayerReader::openCam(): going to read YUV header";
00298         // Now we read the YUV header:
00299         if (!readYUV4MPEG2Header(fifoInput, cols, rows, fps))
00300                 {
00301                 qWarning() << "QVMPlayerReader::openCam(): Warning: Mplayer could not open the requested video source ("
00302                            << qPrintable(urlstring) << ") of type " << qPrintable(schema);
00303                 qDebug() << "QVMPlayerReader::openCam(): Terminating and killing mplayer";
00304                 mplayer->terminate();
00305                 if (not mplayer->waitForFinished(500)) mplayer->kill(); 
00306                 qDebug() << "QVMPlayerReader::openCam(): Deleting pipe and mplayer";
00307                 delete namedPipe;
00308                 delete mplayer;
00309                 qDebug() << "QVMPlayerReader::openCam(): closing fifo";
00310                 fifoInput.close();
00311                 // Finish guarding thread.
00312                 while(not check_thread.isFinished())
00313                 {
00314                         qDebug() << "QVMPlayerReader::openCam(): quitting guarding thread (incorrect mplayer launch)";
00315                         check_thread.quit();
00316                         qDebug() << "QVMPlayerReader::openCam(): CheckOKMPlayerCamera thread quitted after correct launch of mplayer";
00317                         check_thread.wait(100);
00318                 }               
00319                 qDebug() << "QVMPlayerReader::openCam(): guarding thread finished after incorrect launch of mplayer";
00320                 qDebug() << "QVMPlayerReader::openCam(): Returning FALSE";
00321                 cols = 0;
00322                 rows = 0;
00323                 fps = 0;
00324                 frames_grabbed = 0;
00325                 camera_opened = FALSE;
00326                 return FALSE;
00327                 }
00328         qDebug()<< "QVMPlayerReader::openCam(): back from read YUV header: cols = " << cols << " rows = " << rows << ", fps = " << fps;
00329 
00330         // Finish guarding thread.
00331         while(not check_thread.isFinished())
00332         {
00333                 qDebug() << "QVMPlayerReader::openCam(): quitting guarding thread  (correct mplayer launch)";
00334                 check_thread.quit();
00335                 qDebug() << "QVMPlayerReader::openCam(): CheckOKMPlayerCamera thread quitted after correct launch of mplayer";
00336                 check_thread.wait(100);
00337         }               
00338         qDebug() << "QVMPlayerReader::openCam(): CheckOKMPlayerCamera thread finished after correct launch of mplayer";
00339 
00340         // Now we close the fifo and open it again in ReadOnly mode (to avoid readYUV4MPEGFrame to block if we reach
00341         // the end of the mplayer video). There is also another "dirty trick" here: we must open an aux fifo (which we
00342         // close just a little bit later) in order not to leave at any moment the FIFO without readers (which could
00343         // provoke a race condition, because the mplayer process could decide to write in the FIFO just when there
00344         // are not any FIFO open for reading on it):
00345         QFile fifoAux;
00346         fifoAux.setFileName(namedPipe->getOutputFilePath());
00347         fifoAux.open(QIODevice::ReadOnly);
00348         fifoInput.close();
00349         // Here we open the "good" FIFO again, but now in ReadOnly mode:
00350         if(fifoInput.open(QIODevice::ReadOnly|QIODevice::Unbuffered) == -1)
00351                 qFatal("Error opening fifo %s\n", qPrintable(namedPipe->getOutputFilePath()));
00352         fifoAux.close();
00353 
00354         qDebug() << "QVMPlayerReader::openCam(): Header correctly read: cols = "
00355                  << cols << ", rows = " << rows << ", fps = " << fps;
00356 
00357         // If we get here, the header was correctly read, proceed to init MPlayer IO processing.
00358         frames_grabbed = 0;
00359         camera_opened = TRUE;
00360 
00361         // Create adequately sized images (observe 8 byte step alignment by rows):
00362         imgY = QVImage<uChar>(cols, rows, iRoundUp(cols,8));
00363         imgU = QVImage<uChar>(cols/2, rows/2, iRoundUp(cols/2,8));
00364         imgV = QVImage<uChar>(cols/2, rows/2, iRoundUp(cols/2,8));
00365 
00366         // THIS DOES NOT WORK (mplayer does not write anything until later... so we have to read from it in performGrab()):
00367         // while(mplayerIO->time_length == 0)
00368         //      mplayerIO->interpretMPlayerOutput();
00369 
00370         qDebug() << "QVMPlayerReader::openCam() <- sending get_time_length command to mplayer";
00371         mplayer->write("get_time_length\n");
00372         mplayer->waitForReadyRead();
00373 
00374         qDebug() << "QVMPlayerReader::openCam() <- emitting camOpened signal";
00375         emit camOpened();
00376 
00377         qDebug() << "QVMPlayerReader::openCam() <- return";
00378         return TRUE;
00379         }
00380 
00381 void QVMPlayerReader::closeCam()
00382         {
00383         qDebug() << "QVMPlayerReader::closeCam()";
00384 
00385         if (not camera_opened)
00386                 {
00387                 qDebug() << "QVMPlayerReader::closeCam(): camera already closed. Returning";
00388                 return;
00389                 }
00390 
00391         qDebug() << "QVMPlayerReader::closeCam(): closing fifo";
00392         fifoInput.close();
00393 
00394         if(not end_of_video) 
00395                 {
00396                 qDebug() << "QVMPlayerReader::closeCam(): going to send quit command to mplayer";
00397                 mplayer->write("quit\n");
00398                 }
00399         qDebug() << "QVMPlayerReader::closeCam(): going to terminate mplayer";
00400         mplayer->terminate();
00401         qDebug() << "QVMPlayerReader::closeCam(): going to kill mplayer";
00402         mplayer->kill();
00403         qDebug() << "QVMPlayerReader::closeCam(): going to wait for mplayer to finish";
00404         mplayer->waitForFinished();
00405         qDebug() << "QVMPlayerReader::closeCam(): mplayer finished";
00406 
00407         qDebug() << "QVMPlayerReader::closecam(): deleting namedpipe";          
00408         delete namedPipe;
00409 
00410         qDebug() << "QVMPlayerReader::closecam(): deleting QProcess mplayer";
00411         delete mplayer;
00412 
00413         // We reset all the members of the class:
00414         open_options = Default;
00415         path = QString();
00416         schema = QString();
00417         camera_opened = FALSE;
00418         frames_grabbed = 0;
00419         live_camera = FALSE;
00420         imgY = QVImage<uChar>();
00421         imgU = QVImage<uChar>();
00422         imgV = QVImage<uChar>();
00423         cols = 0;
00424         rows = 0;
00425         fps = 0;
00426         time_length = 0;
00427         time_pos = 0;
00428         end_of_video = FALSE;
00429 
00430         qDebug() << "QVMPlayerReader::closeCam() <- emitting camClosed signal";
00431         emit camClosed();
00432 
00433         qDebug() << "QVMPlayerReader::closeCam() <- return";
00434         }
00435 
00436 bool QVMPlayerReader::grab(QVImage<uChar,1> & imageGray)
00437         {
00438         qDebug() << "QVMPlayerReader::grab(imageGray)";
00439         if (performGrab())
00440                 {
00441                 imageGray = imgY;
00442                 return TRUE;
00443                 }
00444         else
00445                 return FALSE;
00446         }
00447 
00448 bool QVMPlayerReader::grab(QVImage<uChar,3> & imageRGB)
00449         {
00450         qDebug() << "QVMPlayerReader::grab(imageRGB)";
00451         if (performGrab())
00452                 {
00453                 imageRGB = QVImage<uChar,3>(imgY.getCols(),imgY.getRows());
00454                 YUV420ToRGB(imgY, imgU, imgV, imageRGB);
00455                 return TRUE;
00456                 }
00457         else
00458                 return FALSE;
00459         }
00460 
00461 bool QVMPlayerReader::grab(QVImage<uChar> &imageY, QVImage<uChar> &imageU, QVImage<uChar> &imageV)
00462         {
00463         qDebug() << "QVMPlayerReader::grab(imageY, imageU, imageV)";
00464         if (performGrab())
00465                 {
00466                 imageY = imgY;
00467                 imageU = imgU;
00468                 imageV = imgV;
00469                 return TRUE;
00470                 }
00471         else
00472                 return FALSE;
00473         }
00474 
00475 void QVMPlayerReader::seekCam(TSeekType seek,double d)
00476         {
00477         qDebug() << "QVMPlayerReader::seekCam(" << static_cast<int>(seek) << "," << d << ")";
00478         if(not camera_opened) return;
00479         QString command = QString("pausing_keep seek ") + QString::number(d) + " " + QString::number(seek) + "\n";
00480         mplayer->write(qPrintable(command)); // pausing keep in fact not needed...
00481         qDebug() << "QVMPlayerReader::seekCam() <~ return";
00482         }



QVision framework. PARP research group, copyright 2007, 2008.