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)
107 requestHeaders.append(QString(
"%1 %2 RTSP/1.0").arg(method, uri));
108 requestHeaders.append(QString(
"User-Agent: MythTV Ceton Recorder"));
111 requestHeaders.append(QString(
"Session: %1").arg(
m_sessionId));
114 for(
int i = 0; i <
headers->count(); i++)
116 const QString& header =
headers->at(i);
117 requestHeaders.append(header);
120 requestHeaders.append(QString(
"\r\n"));
121 QString request = requestHeaders.join(
"\r\n");
124 LOG(VB_RECORD, LOG_DEBUG,
LOC + QString(
"write: %1").arg(request));
125 m_socket->write(request.toLatin1());
133 static const QRegularExpression kFirstLineRE {
"^RTSP/1.0 (\\d+) ([^\r\n]+)" };
134 static const QRegularExpression kHeaderRE { R
"(^([^:]+):\s*([^\r\n]+))" };
135 static const QRegularExpression kBlankLineRE { R
"(^[\r\n]*$)" };
137 bool firstLine =
true;
142 bool ready =
m_socket->waitForReadyRead(30 * 1000);
145 LOG(VB_RECORD, LOG_ERR,
LOC +
"RTSP server did not respond after 30s");
151 QString line =
m_socket->readLine();
152 LOG(VB_RECORD, LOG_DEBUG,
LOC + QString(
"read: %1").arg(line));
154 QRegularExpressionMatch match;
157 match = kFirstLineRE.match(line);
158 if (!match.hasMatch())
162 QString(
"Could not parse first line of response: '%1'")
167 QStringList parts = match.capturedTexts();
174 QString(
"Server couldn't process the request: '%1'")
182 match = kBlankLineRE.match(line);
183 if (match.hasMatch())
break;
185 match = kHeaderRE.match(line);
186 if (!match.hasMatch())
193 QStringList parts = match.capturedTexts();
208 [](
const QString& key) ->
bool
209 {
return key.compare(
"CSeq", Qt::CaseInsensitive) == 0;});
215 LOG(VB_RECORD, LOG_WARNING,
LOC +
216 QString(
"Expected CSeq of %1 but got %2")
222 if (contentLength > 0)
227 while (bytesRead < contentLength)
229 if (
m_socket->bytesAvailable() == 0)
232 int count =
m_socket->read(data+bytesRead, contentLength-bytesRead);
241 LOG(VB_RECORD, LOG_DEBUG,
LOC +
251 static const QRegularExpression kSeparatorRE {
",\\s*" };
275 for (
int i = 0; i < header.size(); i++)
277 QString entry = header[i].trimmed();
284 QStringList
args = entry.split(
"=");
286 parameters.insert(
args[0].trimmed(),
287 args.size() > 1 ?
args[1].trimmed() : QString());
314 headers.append(
"Accept: application/sdp");
320 static const QRegularExpression eol {
"[\r\n]" };
322 QStringList lines =
content.split(eol, Qt::SkipEmptyParts);
326 for (
const QString& line : std::as_const(lines))
328 if (line.startsWith(
"m="))
335 if (!line.startsWith(
"m=video"))
340 QStringList
args = line.split(
" ");
341 if (
args[2] ==
"RTP/AVP" &&
args[3] ==
"33")
347 if (line.startsWith(
"c="))
353 if (line.startsWith(
"a=control:"))
358 QString url = line.mid(10).trimmed();
374 LOG(VB_RECORD, LOG_ERR,
LOC +
"expected content to be type "
375 "\"m=video 0 RTP/AVP 33\" but it appears not to be");
384 ushort &rtpPort, ushort &rtcpPort,
387 LOG(VB_GENERAL, LOG_INFO, QString(
"CetonRTSP: ") +
388 QString(
"Transport: RTP/AVP;unicast;client_port=%1-%2")
389 .arg(clientPort1).arg(clientPort2));
391 QStringList extraHeaders;
393 QString(
"Transport: RTP/AVP;unicast;client_port=%1-%2")
394 .arg(clientPort1).arg(clientPort2));
402 if (session.isEmpty())
404 LOG(VB_RECORD, LOG_ERR,
LOC +
405 "session id not found in SETUP response");
408 if (session.size() < 8)
410 LOG(VB_RECORD, LOG_WARNING,
LOC +
411 "invalid session id received");
415 if (params.contains(
"timeout"))
417 m_timeout = std::chrono::seconds(params[
"timeout"].toInt());
421 if (params.contains(
"ssrc"))
424 ssrc = params[
"ssrc"].toUInt(&ok, 16);
426 if (params.contains(
"server_port"))
428 QString line = params[
"server_port"];
429 QStringList val = line.split(
"-");
431 rtpPort = val[0].toInt();
432 rtcpPort = val.size() > 1 ? val[1].toInt() : 0;
466 LOG(VB_RECORD, LOG_DEBUG,
LOC +
467 QString(
"Start KeepAlive, every %1s").arg(
timeout.count()));
476 LOG(VB_RECORD, LOG_DEBUG,
LOC +
"Stop KeepAlive");
483 LOG(VB_RECORD, LOG_DEBUG,
LOC +
"Sending KeepAlive");
#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_)