MythTV master
satiprtsp.cpp
Go to the documentation of this file.
1
3// C++ includes
4#include <chrono>
5
6// Qt includes
7#include <QRegularExpression>
8#include <QString>
9#include <QStringList>
10#include <QTcpSocket>
11#include <QUrl>
12#include <QVector>
13
14// MythTV includes
17
18#include "rtp/rtcpdatapacket.h"
19#include "rtp/rtppacketbuffer.h"
20#include "rtp/rtptsdatapacket.h"
21#include "rtp/udppacketbuffer.h"
22#include "satiprtcppacket.h"
23#include "satiprtsp.h"
24#include "satipstreamhandler.h"
25
26#define LOC QString("SatIPRTSP[%1]: ").arg(m_inputId)
27#define LOC2 QString("SatIPRTSP[%1](%2): ").arg(m_inputId).arg(m_requestUrl.toString())
28
30
31
33 : m_inputId(inputId)
34{
35 // Call across threads
38}
39
41{
42 emit StopKeepAlive();
43}
44
45bool SatIPRTSP::sendMessage(const QString& msg, QStringList *additionalheaders)
46{
47 QMutexLocker locker(&s_rtspMutex);
48
49 QTcpSocket ctrl_socket;
50 ctrl_socket.connectToHost(m_requestUrl.host(), m_requestUrl.port());
51
52 bool ok = ctrl_socket.waitForConnected(10 * 1000);
53 if (!ok)
54 {
55 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Could not connect to server %1:%2")
56 .arg(m_requestUrl.host()).arg(m_requestUrl.port()));
57 return false;
58 }
59
60 QStringList requestHeaders;
61 requestHeaders.append(QString("%1 %2 RTSP/1.0").arg(msg, m_requestUrl.toString()));
62 requestHeaders.append(QString("User-Agent: MythTV Sat>IP client"));
63 requestHeaders.append(QString("CSeq: %1").arg(++m_cseq));
64
65 if (m_sessionid.length() > 0)
66 {
67 requestHeaders.append(QString("Session: %1").arg(m_sessionid));
68 }
69
70 if (additionalheaders != nullptr)
71 {
72 for (auto& adhdr : *additionalheaders)
73 {
74 requestHeaders.append(adhdr);
75 }
76 }
77
78 requestHeaders.append("\r\n");
79
80 for (const auto& requestLine : requestHeaders)
81 {
82 LOG(VB_RECORD, LOG_DEBUG, LOC + "sendMessage " +
83 QString("write: %1").arg(requestLine.simplified()));
84 }
85
86 QString request = requestHeaders.join("\r\n");
87 ctrl_socket.write(request.toLatin1());
88
89 m_responseHeaders.clear();
90
91 static const QRegularExpression kFirstLineRE { "^RTSP/1.0 (\\d+) ([^\r\n]+)" };
92 static const QRegularExpression kHeaderRE { R"(^([^:]+):\s*([^\r\n]+))" };
93 static const QRegularExpression kBlankLineRE { R"(^[\r\n]*$)" };
94
95 bool firstLine = true;
96 while (true)
97 {
98 if (!ctrl_socket.canReadLine())
99 {
100 bool ready = ctrl_socket.waitForReadyRead(10 * 1000);
101 if (!ready)
102 {
103 LOG(VB_RECORD, LOG_ERR, LOC + "RTSP server did not respond after 10s");
104 return false;
105 }
106 continue;
107 }
108
109 QString line = ctrl_socket.readLine();
110 LOG(VB_RECORD, LOG_DEBUG, LOC + "sendMessage " +
111 QString("read: %1").arg(line.simplified()));
112
113 QRegularExpressionMatch match;
114 if (firstLine)
115 {
116 match = kFirstLineRE.match(line);
117 if (!match.hasMatch())
118 {
119 LOG(VB_RECORD, LOG_WARNING, LOC +
120 QString("Could not parse first line of response: '%1'")
121 .arg(line.simplified()));
122 return false;
123 }
124
125 QStringList parts = match.capturedTexts();
126 int responseCode = parts.at(1).toInt();
127 const QString& responseMsg = parts.at(2);
128
129 LOG(VB_RECORD, LOG_DEBUG, LOC + QString("response code:%1 message:%2")
130 .arg(responseCode).arg(responseMsg));
131
132 if (responseCode != 200)
133 {
134 LOG(VB_RECORD, LOG_WARNING, LOC +
135 QString("Server couldn't process the request: '%1'")
136 .arg(responseMsg));
137 return false;
138 }
139 firstLine = false;
140 continue;
141 }
142
143 match = kBlankLineRE.match(line);
144 if (match.hasMatch()) break;
145
146 match = kHeaderRE.match(line);
147 if (!match.hasMatch())
148 {
149 LOG(VB_RECORD, LOG_WARNING, LOC +
150 QString("Could not parse response header: '%1'")
151 .arg(line.simplified()));
152 return false;
153 }
154 QStringList parts = match.capturedTexts();
155 m_responseHeaders.insert(parts.at(1).toUpper(), parts.at(2));
156 }
157
158 QString cSeq;
159
160 if (m_responseHeaders.contains("CSEQ"))
161 {
162 cSeq = m_responseHeaders["CSEQ"];
163 }
164
165 if (cSeq != QString("%1").arg(m_cseq))
166 {
167 LOG(VB_RECORD, LOG_WARNING, LOC +
168 QString("Expected CSeq of %1 but got %2").arg(m_cseq).arg(cSeq));
169 }
170
171 ctrl_socket.disconnectFromHost();
172 if (ctrl_socket.state() != QAbstractSocket::UnconnectedState)
173 {
174 ctrl_socket.waitForDisconnected();
175 }
176
177 return true;
178}
179
180bool SatIPRTSP::Setup(const QUrl& url, ushort clientPort1, ushort clientPort2)
181{
182 m_requestUrl = url;
183 LOG(VB_RECORD, LOG_DEBUG, LOC2 + QString("SETUP"));
184
185 if (m_requestUrl.port() != 554)
186 {
187 LOG(VB_RECORD, LOG_WARNING, LOC +
188 QString("Port %1 is used but SatIP specifies RTSP port 554").arg(m_requestUrl.port()));
189 }
190
191 if (m_requestUrl.port() < 1)
192 {
193 LOG(VB_RECORD, LOG_ERR, LOC +
194 QString("Invalid port %1 using 554 instead").arg(m_requestUrl.port()));
195 m_requestUrl.setPort(554);
196 }
197
198
199 QStringList headers;
200 headers.append(
201 QString("Transport: RTP/AVP;unicast;client_port=%1-%2")
202 .arg(clientPort1).arg(clientPort2));
203
204 if (!sendMessage("SETUP", &headers))
205 {
206 LOG(VB_RECORD, LOG_ERR, LOC + "Failed to send SETUP message");
207 return false;
208 }
209
210 if (m_responseHeaders.contains("COM.SES.STREAMID"))
211 {
212 m_streamid = m_responseHeaders["COM.SES.STREAMID"];
213 }
214 else
215 {
216 LOG(VB_RECORD, LOG_ERR, LOC + "SETUP response did not contain the com.ses.streamID field");
217 return false;
218 }
219
220 if (m_responseHeaders.contains("SESSION"))
221 {
222 static const QRegularExpression sessionTimeoutRegex {
223 "^([^\\r\\n]+);timeout=([0-9]+)?", QRegularExpression::CaseInsensitiveOption };
224 auto match = sessionTimeoutRegex.match(m_responseHeaders["SESSION"]);
225 if (!match.hasMatch())
226 {
227 LOG(VB_RECORD, LOG_ERR, LOC +
228 QString("Failed to extract session id from session header ('%1')")
229 .arg(m_responseHeaders["Session"]));
230 }
231
232 m_sessionid = match.captured(1);
233 m_timeout = match.capturedLength(2) > 0
234 ? std::chrono::seconds(match.capturedView(2).toInt() / 2)
235 : 30s;
236
237 LOG(VB_RECORD, LOG_INFO, LOC + QString("Sat>IP protocol timeout:%1 s")
238 .arg(m_timeout.count()));
239 }
240 else
241 {
242 LOG(VB_RECORD, LOG_ERR, LOC + "SETUP response did not contain the Session field");
243 return false;
244 }
245
246 LOG(VB_RECORD, LOG_DEBUG, LOC +
247 QString("Setup completed, sessionID = %1, streamID = %2, timeout = %3s")
249 .arg(duration_cast<std::chrono::seconds>(m_timeout).count()));
250
251 return true;
252}
253
254bool SatIPRTSP::Play(const QString &pids_str)
255{
256 LOG(VB_RECORD, LOG_DEBUG, LOC + "Play(pids_str) " + pids_str);
257
258 m_requestUrl.setQuery(pids_str);
259 m_requestUrl.setPath(QString("/stream=%1").arg(m_streamid));
260
261 if (!sendMessage("PLAY"))
262 {
263 LOG(VB_RECORD, LOG_ERR, LOC + "Failed to send PLAY message");
264 return false;
265 }
266
267 emit StartKeepAlive();
268
269 return true;
270}
271
273{
274 LOG(VB_RECORD, LOG_DEBUG, LOC2 + "TEARDOWN");
275 emit StopKeepAlive();
276
277 m_requestUrl.setQuery(QString());
278 m_requestUrl.setPath(QString("/stream=%1").arg(m_streamid));
279
280 bool result = sendMessage("TEARDOWN");
281
282 if (!result)
283 {
284 LOG(VB_RECORD, LOG_ERR, LOC + "Teardown failed");
285 }
286
287 QMutexLocker locker(&s_rtspMutex);
288
289 m_sessionid.clear();
290 m_streamid.clear();
291
292 return result;
293}
294
296{
297 if (m_timer)
298 return;
299 auto timeout = std::max(m_timeout - 5s, 5s);
300 m_timer = startTimer(timeout);
301 LOG(VB_RECORD, LOG_INFO, LOC + QString("StartKeepAliveRequested(%1) m_timer:%2")
302 .arg(timeout.count()).arg(m_timer));
303}
304
306{
307 LOG(VB_RECORD, LOG_INFO, LOC + QString("StopKeepAliveRequested() m_timer:%1").arg(m_timer));
308 if (m_timer)
309 {
310 killTimer(m_timer);
311 }
312 m_timer = 0;
313}
314
315void SatIPRTSP::timerEvent(QTimerEvent* timerEvent)
316{
317 LOG(VB_RECORD, LOG_INFO, LOC + QString("Sending KeepAlive timer %1").arg(timerEvent->timerId()));
318
319 m_requestUrl.setPath("/");
320 m_requestUrl.setQuery(QString());
321
322 sendMessage("OPTIONS");
323}
bool sendMessage(const QString &msg, QStringList *additionalHeaders=nullptr)
Definition: satiprtsp.cpp:45
std::chrono::seconds m_timeout
Definition: satiprtsp.h:60
uint m_cseq
Definition: satiprtsp.h:54
QUrl m_requestUrl
Definition: satiprtsp.h:53
bool Setup(const QUrl &url, ushort clientPort1, ushort clientPort2)
Definition: satiprtsp.cpp:180
int m_timer
Definition: satiprtsp.h:59
QString m_streamid
Definition: satiprtsp.h:56
bool Play(const QString &pids_str)
Definition: satiprtsp.cpp:254
~SatIPRTSP() override
Definition: satiprtsp.cpp:40
void StopKeepAlive(void)
void StopKeepAliveRequested(void)
Definition: satiprtsp.cpp:305
static QMutex s_rtspMutex
Definition: satiprtsp.h:62
void StartKeepAlive(void)
void StartKeepAliveRequested(void)
Definition: satiprtsp.cpp:295
void timerEvent(QTimerEvent *timerEvent) override
Definition: satiprtsp.cpp:315
QMap< QString, QString > m_responseHeaders
Definition: satiprtsp.h:57
QString m_sessionid
Definition: satiprtsp.h:55
SatIPRTSP(int inputId)
Definition: satiprtsp.cpp:32
bool Teardown()
Definition: satiprtsp.cpp:272
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
#define LOC
-*- Mode: c++ -*-
Definition: satiprtsp.cpp:26
#define LOC2
Definition: satiprtsp.cpp:27