7 #include <QRegularExpression>
25 #if QT_VERSION < QT_VERSION_CHECK(5,15,2)
26 #define capturedView capturedRef
29 #define LOC QString("SatIPRTSP[%1]: ").arg(m_streamHandler->m_inputId)
30 #define LOC2 QString("SatIPRTSP[%1](%2): ").arg(m_streamHandler->m_inputId).arg(m_requestUrl.toString())
38 : m_streamHandler(handler)
52 QAbstractSocket::DefaultForPlatform))
54 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Failed to bind RTP socket"));
59 LOG(VB_RECORD, LOG_INFO,
LOC + QString(
"RTP socket bound to port %1 (0x%2)")
60 .arg(port).arg(port,2,16,QChar(
'0')));
69 QAbstractSocket::DefaultForPlatform))
71 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Failed to bind RTCP socket to port %1").arg(port));
75 LOG(VB_RECORD, LOG_INFO,
LOC + QString(
"RTCP socket bound to port %1 (0x%2)")
76 .arg(port).arg(port,2,16,QChar(
'0')));
80 uint desiredsize = 8000000;
82 if (newsize >= desiredsize)
84 LOG(VB_RECORD, LOG_INFO,
LOC + QString(
"RTP UDP socket receive buffer size set to %1").arg(newsize));
88 LOG(VB_GENERAL, LOG_INFO,
LOC +
"RTP UDP socket receive buffer too small\n" +
89 QString(
"\tRTP UDP socket receive buffer size set to %1 but requested %2\n").arg(newsize).arg(desiredsize) +
90 QString(
"\tTo prevent UDP packet loss increase net.core.rmem_max e.g. with this command:\n") +
91 QString(
"\tsudo sysctl -w net.core.rmem_max=%1\n").arg(desiredsize) +
92 QString(
"\tand restart mythbackend."));
107 LOG(VB_RECORD, LOG_DEBUG,
LOC2 + QString(
"SETUP"));
109 if (url.port() != 554)
111 LOG(VB_RECORD, LOG_WARNING,
LOC +
"SatIP specifies RTSP port 554 to be used");
116 LOG(VB_RECORD, LOG_ERR,
LOC + QString(
"Invalid port %1").arg(url.port()));
122 QString(
"Transport: RTP/AVP;unicast;client_port=%1-%2")
127 LOG(VB_RECORD, LOG_ERR,
LOC +
"Failed to send SETUP message");
131 if (
m_headers.contains(
"COM.SES.STREAMID"))
137 LOG(VB_RECORD, LOG_ERR,
LOC +
"SETUP response did not contain the com.ses.streamID field");
143 static const QRegularExpression sessionTimeoutRegex {
144 "^([^\\r\\n]+);timeout=([0-9]+)?", QRegularExpression::CaseInsensitiveOption };
145 auto match = sessionTimeoutRegex.match(
m_headers[
"SESSION"]);
146 if (!match.hasMatch())
148 LOG(VB_RECORD, LOG_ERR,
LOC +
149 QString(
"Failed to extract session id from session header ('%1')")
155 ? std::chrono::seconds(match.capturedView(2).toInt() / 2)
158 LOG(VB_RECORD, LOG_DEBUG,
LOC + QString(
"Sat>IP protocol timeout:%1 ms")
163 LOG(VB_RECORD, LOG_ERR,
LOC +
"SETUP response did not contain the Session field");
167 LOG(VB_RECORD, LOG_DEBUG,
LOC +
168 QString(
"Setup completed, sessionID = %1, streamID = %2, timeout = %3s")
170 .arg(duration_cast<std::chrono::seconds>(
m_timeout).count()));
182 LOG(VB_RECORD, LOG_DEBUG,
LOC2 +
"PLAY");
186 url.setPath(QString(
"/stream=%1").arg(
m_streamid));
188 QString pids_str = QString(
"pids=%1").arg(!pids.empty() ? pids.join(
",") :
"none");
189 LOG(VB_RECORD, LOG_INFO,
LOC +
"Play(pids) " + pids_str);
192 if (pids.size() > 32)
194 LOG(VB_RECORD, LOG_INFO,
LOC +
195 QString(
"Receive full TS, number of PIDs:%1 is more than 32").arg(pids.size()));
196 LOG(VB_RECORD, LOG_DEBUG,
LOC + pids_str);
197 pids_str = QString(
"pids=all");
199 url.setQuery(pids_str);
200 LOG(VB_RECORD, LOG_DEBUG,
LOC + pids_str);
204 LOG(VB_RECORD, LOG_ERR,
LOC +
"Failed to send PLAY message");
216 LOG(VB_RECORD, LOG_DEBUG,
LOC2 +
"TEARDOWN");
220 url.setQuery(QString());
221 url.setPath(QString(
"/stream=%1").arg(
m_streamid));
227 LOG(VB_RECORD, LOG_ERR,
LOC +
"Teardown failed");
270 QTcpSocket ctrl_socket;
271 ctrl_socket.connectToHost(url.host(), url.port());
273 bool ok = ctrl_socket.waitForConnected(30 * 1000);
276 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Could not connect to server %1:%2").arg(url.host()).arg(url.port()));
281 headers.append(QString(
"%1 %2 RTSP/1.0").arg(msg).arg(url.toString()));
282 headers.append(QString(
"User-Agent: MythTV Sat>IP client"));
283 headers.append(QString(
"CSeq: %1").arg(++
m_cseq));
287 headers.append(QString(
"Session: %1").arg(
m_sessionid));
290 if (additionalheaders !=
nullptr)
292 for (
auto& adhdr : *additionalheaders)
294 headers.append(adhdr);
298 headers.append(
"\r\n");
300 for (
const auto& requestLine : headers)
302 LOG(VB_RECORD, LOG_DEBUG,
LOC +
"sendMessage " +
303 QString(
"write: %1").arg(requestLine.simplified()));
306 QString request = headers.join(
"\r\n");
307 ctrl_socket.write(request.toLatin1());
309 QRegularExpression firstLineRE {
"^RTSP/1.0 (\\d+) ([^\r\n]+)" };
310 QRegularExpression headerRE { R
"(^([^:]+):\s*([^\r\n]+))" };
311 QRegularExpression blankLineRE { R"(^[\r\n]*$)" };
313 bool firstLine =
true;
316 if (!ctrl_socket.canReadLine())
318 bool ready = ctrl_socket.waitForReadyRead(10 * 1000);
321 LOG(VB_RECORD, LOG_ERR,
LOC +
"RTSP server did not respond after 10s");
327 QString line = ctrl_socket.readLine();
328 LOG(VB_RECORD, LOG_DEBUG,
LOC +
"sendMessage " +
329 QString(
"read: %1").arg(line.simplified()));
331 QRegularExpressionMatch match;
334 match = firstLineRE.match(line);
335 if (!match.hasMatch())
340 QStringList parts = match.capturedTexts();
341 int responseCode = parts.at(1).toInt();
342 const QString& responseMsg = parts.at(2);
344 LOG(VB_RECORD, LOG_DEBUG,
LOC + QString(
"response code:%1 message:%2")
345 .arg(responseCode).arg(responseMsg));
347 if (responseCode != 200)
355 match = blankLineRE.match(line);
356 if (match.hasMatch())
break;
358 match = headerRE.match(line);
359 if (!match.hasMatch())
363 QStringList parts = match.capturedTexts();
364 m_headers.insert(parts.at(1).toUpper(), parts.at(2));
374 if (cSeq != QString(
"%1").arg(
m_cseq))
376 LOG(VB_RECORD, LOG_WARNING,
LOC + QString(
"Expected CSeq of %1 but got %2").arg(
m_cseq).arg(cSeq));
379 ctrl_socket.disconnectFromHost();
380 if (ctrl_socket.state() != QAbstractSocket::UnconnectedState)
382 ctrl_socket.waitForDisconnected();
393 LOG(VB_RECORD, LOG_INFO,
LOC + QString(
"startKeepAliveRequested(%1) m_timer:%2")
399 LOG(VB_RECORD, LOG_INFO,
LOC + QString(
"stopKeepAliveRequested() m_timer:%1").arg(
m_timer));
410 LOG(VB_RECORD, LOG_INFO,
LOC + QString(
"Sending KeepAlive timer %1").arg(
timerEvent->timerId()));
424 #define LOC_RH QString("SatIPRTSP[%1]: ").arg(m_parent->m_streamHandler->m_inputId)
428 , m_socket(new QUdpSocket(this))
432 QString(
"Starting read helper for UDP (RTP) socket"));
445 while (
m_socket->hasPendingDatagrams())
448 quint16 senderPort = 0;
452 data.resize(
m_socket->pendingDatagramSize());
453 m_socket->readDatagram(data.data(), data.size(), &sender, &senderPort);
463 #define LOC_RTCP QString("SatIPRTCP[%1]: ").arg(m_parent->m_streamHandler->m_inputId)
467 , m_socket(new QUdpSocket(this))
471 QString(
"Starting read helper for UDP (RTCP) socket"));
485 while (
m_socket->hasPendingDatagrams())
488 LOG(VB_RECORD, LOG_INFO,
"SatIPRTSP_RH " +
489 QString(
"Processing RTCP packet(pendingDatagramSize:%1)")
490 .arg(
m_socket->pendingDatagramSize()));
494 quint16 senderPort = 0;
496 QByteArray buf = QByteArray(
m_socket->pendingDatagramSize(), Qt::Uninitialized);
497 m_socket->readDatagram(buf.data(), buf.size(), &sender, &senderPort);
502 LOG(VB_GENERAL, LOG_ERR,
LOC_RTCP +
"Invalid RTCP packet received");
506 QStringList data = pkt.
Data().split(
";");
510 while (!found && i < data.length())
512 const QString& item = data.at(i);
514 if (item.startsWith(
"tuner="))
517 QStringList tuner = item.split(
",");
519 if (tuner.length() > 3)
521 int level = tuner.at(1).toInt();
522 bool lock = tuner.at(2).toInt() != 0;
523 int quality = tuner.at(3).toInt();
526 QString(
"Tuner lock:%1 level:%2 quality:%3").arg(lock).arg(level).arg(quality));
538 #define LOC_WH QString("SatIPRTSP[%1]: ").arg(m_streamHandler->m_inputId)
542 , m_streamHandler(handler)
567 ((exp_seq_num & 0xFFFF) != (seq_num & 0xFFFF)))
581 QString(
"RTP Sequence number mismatch %1!=%2, discarded %3 RTP packets")
582 .arg(seq_num).arg(exp_seq_num).arg(discarded));
589 QString(
"Processing RTP packet(seq:%1 ts:%2, ts_data_size:%3) %4")
599 if (!streamDataList.isEmpty())
601 const unsigned char *data_buffer = ts_packet.
GetTSData();
604 for (
auto sit = streamDataList.cbegin(); sit != streamDataList.cend(); ++sit)
606 remainder = sit.key()->ProcessData(data_buffer, data_length);
616 QString(
"RTP data_length = %1 remainder = %2")
633 QVariant ss = socket->socketOption(QAbstractSocket::ReceiveBufferSizeSocketOption);
634 uint oldsize = ss.toUInt();
635 if (oldsize < rcvbuffersize*2)
637 socket->setSocketOption(QAbstractSocket::ReceiveBufferSizeSocketOption, rcvbuffersize);
639 ss = socket->socketOption(QAbstractSocket::ReceiveBufferSizeSocketOption);
640 uint newsize = ss.toUInt()/2;