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 #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 
45 bool 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 
180 bool 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")
248  .arg(m_sessionid, m_streamid)
249  .arg(duration_cast<std::chrono::seconds>(m_timeout).count()));
250 
251  return true;
252 }
253 
254 bool 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 
315 void 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 }
SatIPRTSP::SatIPRTSP
SatIPRTSP(int m_inputId)
Definition: satiprtsp.cpp:32
rtcpdatapacket.h
hardwareprofile.smolt.timeout
float timeout
Definition: smolt.py:102
SatIPRTSP::Play
bool Play(const QString &pids_str)
Definition: satiprtsp.cpp:254
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:315
LOC
#define LOC
-*- Mode: c++ -*-
Definition: satiprtsp.cpp:26
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
music163.headers
dictionary headers
Definition: music163.py:26
SatIPRTSP::Teardown
bool Teardown()
Definition: satiprtsp.cpp:272
rtppacketbuffer.h
SatIPRTSP::~SatIPRTSP
~SatIPRTSP() override
Definition: satiprtsp.cpp:40
SatIPRTSP::sendMessage
bool sendMessage(const QString &msg, QStringList *additionalHeaders=nullptr)
Definition: satiprtsp.cpp:45
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:295
SatIPRTSP::StopKeepAliveRequested
void StopKeepAliveRequested(void)
Definition: satiprtsp.cpp:305
satiprtsp.h
LOC2
#define LOC2
Definition: satiprtsp.cpp:27
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:180
SatIPRTSP::m_timeout
std::chrono::seconds m_timeout
Definition: satiprtsp.h:60
satiprtcppacket.h
SatIPRTSP::StopKeepAlive
void StopKeepAlive(void)
mythsocket.h