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