00001
00007 #include <QStringList>
00008 #include <QTcpSocket>
00009
00010
00011 #include "cetonrtsp.h"
00012 #include "mythlogging.h"
00013
00014
00015 #define LOC QString("CetonRTSP(%1-%2): ").arg(_ip).arg(_tuner)
00016
00017 QMutex CetonRTSP::_rtspMutex;
00018
00019 CetonRTSP::CetonRTSP(const QString &ip, uint tuner, ushort port) :
00020 _ip(ip),
00021 _port(port),
00022 _tuner(tuner),
00023 _sequenceNumber(0),
00024 _sessionNumber(0),
00025 _responseCode(-1)
00026 {
00027 _requestUrl = QString("rtsp://%1:%2/cetonmpeg%3")
00028 .arg(ip).arg(port).arg(tuner);
00029 }
00030
00031 bool CetonRTSP::ProcessRequest(
00032 const QString &method, const QStringList* headers)
00033 {
00034 QMutexLocker locker(&_rtspMutex);
00035 QTcpSocket socket;
00036 socket.connectToHost(_ip, _port);
00037
00038 QStringList requestHeaders;
00039 requestHeaders.append(QString("%1 %2 RTSP/1.0").arg(method, _requestUrl));
00040 requestHeaders.append(QString("User-Agent: MythTV Ceton Recorder"));
00041 requestHeaders.append(QString("CSeq: %1").arg(++_sequenceNumber));
00042 if (_sessionNumber > 0)
00043 requestHeaders.append(QString("Session: %1").arg(_sessionNumber));
00044 if (headers != NULL)
00045 {
00046 for(int i = 0; i < headers->count(); i++)
00047 {
00048 QString header = headers->at(i);
00049 requestHeaders.append(header);
00050 }
00051 }
00052 requestHeaders.append(QString("\r\n"));
00053 QString request = requestHeaders.join("\r\n");
00054
00055
00056 LOG(VB_RECORD, LOG_DEBUG, LOC + QString("write: %1").arg(request));
00057 socket.write(request.toAscii());
00058
00059 _responseHeaders.clear();
00060 _responseContent.clear();
00061
00062 QRegExp firstLineRegex(
00063 "^RTSP/1.0 (\\d+) ([^\r\n]+)", Qt::CaseSensitive, QRegExp::RegExp2);
00064 QRegExp headerRegex(
00065 "^([^:]+):\\s*([^\\r\\n]+)", Qt::CaseSensitive, QRegExp::RegExp2);
00066 QRegExp blankLineRegex(
00067 "^[\\r\\n]*$", Qt::CaseSensitive, QRegExp::RegExp2);
00068
00069 bool firstLine = true;
00070 while (true)
00071 {
00072 if (!socket.canReadLine())
00073 {
00074 bool ready = socket.waitForReadyRead();
00075 if (!ready)
00076 {
00077 LOG(VB_RECORD, LOG_ERR, LOC + "RTSP server did not respond");
00078 return false;
00079 }
00080 continue;
00081 }
00082
00083 QString line = socket.readLine();
00084 LOG(VB_RECORD, LOG_DEBUG, LOC + QString("read: %1").arg(line));
00085
00086 if (firstLine)
00087 {
00088 if (firstLineRegex.indexIn(line) == -1)
00089 {
00090 _responseCode = -1;
00091 _responseMessage =
00092 QString("Could not parse first line of response: '%1'")
00093 .arg(line);
00094 return false;
00095 }
00096
00097 QStringList parts = firstLineRegex.capturedTexts();
00098 _responseCode = parts.at(1).toInt();
00099 _responseMessage = parts.at(2);
00100
00101 firstLine = false;
00102 continue;
00103 }
00104
00105 if (blankLineRegex.indexIn(line) != -1) break;
00106
00107 if (headerRegex.indexIn(line) == -1)
00108 {
00109 _responseCode = -1;
00110 _responseMessage = QString("Could not parse response header: '%1'")
00111 .arg(line);
00112 return false;
00113 }
00114 QStringList parts = headerRegex.capturedTexts();
00115 _responseHeaders.insert(parts.at(1), parts.at(2));
00116 }
00117
00118 QString cSeq = _responseHeaders.value("CSeq");
00119 if (cSeq != QString("%1").arg(_sequenceNumber))
00120 {
00121 LOG(VB_RECORD, LOG_WARNING, LOC +
00122 QString("Expected CSeq of %1 but got %2")
00123 .arg(_sequenceNumber).arg(cSeq));
00124 }
00125
00126 _responseContent.clear();
00127 int contentLength = _responseHeaders.value("Content-Length").toInt();
00128 if (contentLength > 0)
00129 {
00130 _responseContent.resize(contentLength);
00131 char* data = _responseContent.data();
00132 int bytesRead = 0;
00133 while (bytesRead < contentLength)
00134 {
00135 if (socket.bytesAvailable() == 0)
00136 socket.waitForReadyRead();
00137
00138 int count = socket.read(data+bytesRead, contentLength-bytesRead);
00139 if (count == -1)
00140 {
00141 _responseCode = -1;
00142 _responseMessage = "Could not read response content";
00143 return false;
00144 }
00145 bytesRead += count;
00146 }
00147 }
00148 return true;
00149 }
00150
00151 bool CetonRTSP::GetOptions(QStringList &options)
00152 {
00153 if (ProcessRequest("OPTIONS"))
00154 {
00155 options = _responseHeaders.value("Public").split(",");
00156 return true;
00157 }
00158 return false;
00159 }
00160
00161 bool CetonRTSP::Describe(void)
00162 {
00163 if (!ProcessRequest("DESCRIBE"))
00164 return false;
00165
00166 if (!_responseContent.contains("m=video 0 RTP/AVP 33"))
00167 {
00168 LOG(VB_RECORD, LOG_ERR, LOC + "expected content to be type "
00169 "\"m=video 0 RTP/AVP 33\" but it appears not to be");
00170 return false;
00171 }
00172
00173 return true;
00174 }
00175
00176 bool CetonRTSP::Setup(ushort clientPort1, ushort clientPort2)
00177 {
00178 QStringList extraHeaders;
00179 extraHeaders.append(
00180 QString("Transport: RTP/AVP;unicast;client_port=%1-%2")
00181 .arg(clientPort1).arg(clientPort2));
00182
00183 if (!ProcessRequest("SETUP", &extraHeaders))
00184 return false;
00185
00186 _sessionNumber = _responseHeaders.value("Session").toInt();
00187 if (_sessionNumber == 0)
00188 {
00189 LOG(VB_RECORD, LOG_ERR, LOC +
00190 "session number not found in SETUP response");
00191 return false;
00192 }
00193
00194 return true;
00195 }
00196
00197 bool CetonRTSP::Play(void)
00198 {
00199 return ProcessRequest("PLAY");
00200 }
00201
00202 bool CetonRTSP::Teardown(void)
00203 {
00204 bool result = ProcessRequest("TEARDOWN");
00205 _sessionNumber = 0;
00206 return result;
00207 }