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