11#include <QRegularExpression>
24#define LOC QString("CetonRTSP(%1): ").arg(m_requestUrl.toString())
52 const QString &method,
const QStringList*
headers,
53 bool use_control,
bool waitforanswer,
const QString &alternative)
72 QAbstractSocket::ReadWrite);
73 bool ok =
m_socket->waitForConnected();
77 LOG(VB_GENERAL, LOG_ERR,
LOC +
78 QString(
"Could not connect to server %1:%2")
91 while ((avail =
m_socket->bytesAvailable()) > 0)
93 trash.resize(std::max((
uint)trash.size(), avail));
99 QStringList requestHeaders;
101 if (!alternative.isEmpty())
103 else if (use_control)
108 requestHeaders.append(QString(
"%1 %2 RTSP/1.0").arg(method, uri));
109 requestHeaders.append(QString(
"User-Agent: MythTV Ceton Recorder"));
112 requestHeaders.append(QString(
"Session: %1").arg(
m_sessionId));
115 for(
int i = 0; i <
headers->count(); i++)
117 const QString& header =
headers->at(i);
118 requestHeaders.append(header);
121 requestHeaders.append(QString(
"\r\n"));
122 QString request = requestHeaders.join(
"\r\n");
125 LOG(VB_RECORD, LOG_DEBUG,
LOC + QString(
"write: %1").arg(request));
126 m_socket->write(request.toLatin1());
134 static const QRegularExpression kFirstLineRE {
"^RTSP/1.0 (\\d+) ([^\r\n]+)" };
135 static const QRegularExpression kHeaderRE { R
"(^([^:]+):\s*([^\r\n]+))" };
136 static const QRegularExpression kBlankLineRE { R
"(^[\r\n]*$)" };
138 bool firstLine =
true;
143 bool ready =
m_socket->waitForReadyRead(30 * 1000);
146 LOG(VB_RECORD, LOG_ERR,
LOC +
"RTSP server did not respond after 30s");
152 QString line =
m_socket->readLine();
153 LOG(VB_RECORD, LOG_DEBUG,
LOC + QString(
"read: %1").arg(line));
155 QRegularExpressionMatch match;
158 match = kFirstLineRE.match(line);
159 if (!match.hasMatch())
163 QString(
"Could not parse first line of response: '%1'")
168 QStringList parts = match.capturedTexts();
175 QString(
"Server couldn't process the request: '%1'")
183 match = kBlankLineRE.match(line);
184 if (match.hasMatch())
break;
186 match = kHeaderRE.match(line);
187 if (!match.hasMatch())
194 QStringList parts = match.capturedTexts();
209 [](
const QString& key) ->
bool
210 {
return key.compare(
"CSeq", Qt::CaseInsensitive) == 0;});
216 LOG(VB_RECORD, LOG_WARNING,
LOC +
217 QString(
"Expected CSeq of %1 but got %2")
223 if (contentLength > 0)
228 while (bytesRead < contentLength)
230 if (
m_socket->bytesAvailable() == 0)
233 int count =
m_socket->read(data+bytesRead, contentLength-bytesRead);
242 LOG(VB_RECORD, LOG_DEBUG,
LOC +
252 static const QRegularExpression kSeparatorRE {
",\\s*" };
276 for (
int i = 0; i < header.size(); i++)
278 QString entry = header[i].trimmed();
285 QStringList
args = entry.split(
"=");
287 parameters.insert(
args[0].trimmed(),
288 args.size() > 1 ?
args[1].trimmed() : QString());
315 headers.append(
"Accept: application/sdp");
321 static const QRegularExpression eol {
"[\r\n]" };
323 QStringList lines =
content.split(eol, Qt::SkipEmptyParts);
327 for (
const QString& line : std::as_const(lines))
329 if (line.startsWith(
"m="))
336 if (!line.startsWith(
"m=video"))
341 QStringList
args = line.split(
" ");
342 if (
args[2] ==
"RTP/AVP" &&
args[3] ==
"33")
348 if (line.startsWith(
"c="))
354 if (line.startsWith(
"a=control:"))
359 QString url = line.mid(10).trimmed();
375 LOG(VB_RECORD, LOG_ERR,
LOC +
"expected content to be type "
376 "\"m=video 0 RTP/AVP 33\" but it appears not to be");
385 ushort &rtpPort, ushort &rtcpPort,
388 LOG(VB_GENERAL, LOG_INFO, QString(
"CetonRTSP: ") +
389 QString(
"Transport: RTP/AVP;unicast;client_port=%1-%2")
390 .arg(clientPort1).arg(clientPort2));
392 QStringList extraHeaders;
394 QString(
"Transport: RTP/AVP;unicast;client_port=%1-%2")
395 .arg(clientPort1).arg(clientPort2));
403 if (session.isEmpty())
405 LOG(VB_RECORD, LOG_ERR,
LOC +
406 "session id not found in SETUP response");
409 if (session.size() < 8)
411 LOG(VB_RECORD, LOG_WARNING,
LOC +
412 "invalid session id received");
416 if (params.contains(
"timeout"))
418 m_timeout = std::chrono::seconds(params[
"timeout"].toInt());
422 if (params.contains(
"ssrc"))
425 ssrc = params[
"ssrc"].toUInt(&ok, 16);
427 if (params.contains(
"server_port"))
429 QString line = params[
"server_port"];
430 QStringList val = line.split(
"-");
432 rtpPort = val[0].toInt();
433 rtcpPort = val.size() > 1 ? val[1].toInt() : 0;
467 LOG(VB_RECORD, LOG_DEBUG,
LOC +
468 QString(
"Start KeepAlive, every %1s").arg(
timeout.count()));
477 LOG(VB_RECORD, LOG_DEBUG,
LOC +
"Stop KeepAlive");
484 LOG(VB_RECORD, LOG_DEBUG,
LOC +
"Sending KeepAlive");
495#include "moc_cetonrtsp.cpp"
#define LOC
-*- Mode: c++ -*- CetonRTSP Copyright (c) 2011 Ronald Frazier Distributed as part of MythTV under GPL...
QMap< QString, QString > Params
CetonRTSP(const QString &ip, uint tuner, ushort port)
QUrl GetBaseUrl(void)
Return the base URL for the last DESCRIBE answer.
bool Setup(ushort clientPort1, ushort clientPort2, ushort &rtpPort, ushort &rtcpPort, uint32_t &ssrc)
static QMutex s_rtspMutex
QByteArray m_responseContent
QString readParameters(const QString &key, Params ¶meters)
readParameters.
bool GetOptions(QStringList &options)
bool ProcessRequest(const QString &method, const QStringList *headers=nullptr, bool use_control=false, bool waitforanswer=true, const QString &alternative=QString())
void timerEvent(QTimerEvent *event) override
void StartKeepAlive(void)
std::chrono::seconds m_timeout
QString m_responseMessage
#define LOG(_MASK_, _LEVEL_, _QSTRING_)