10#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
11#include <QStringConverter>
30#if OPENSSL_VERSION_NUMBER < 0x030000000L
31#define EVP_PKEY_get_id EVP_PKEY_id
32#define EVP_PKEY_get_size EVP_PKEY_size
35#define LOC QString("RAOP Conn: ")
44static constexpr uint8_t
SYNC { 0x54 };
63#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
64 m_q->setCodec(
"UTF-8");
66 m_q->setEncoding(QStringConverter::Utf8);
75 LOG(VB_PLAYBACK, LOG_DEBUG,
76 LOC + QString(
"Sending(%1): ").arg(str.length()) + str.trimmed());
83 QTextStream *
m_q {
nullptr};
87 QByteArray
id,
int port)
90 m_hardwareId(
std::move(id)),
92 m_cctx(EVP_CIPHER_CTX_new()),
95#if OPENSSL_VERSION_NUMBER < 0x030000000L
99 m_cipher = EVP_CIPHER_fetch(
nullptr,
"AES-128-CBC",
nullptr);
110 client->deleteLater();
141 nc->UnRegister(
this,
m_id);
144 EVP_CIPHER_CTX_free(
m_cctx);
146#if OPENSSL_VERSION_NUMBER >= 0x030000000L
219 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
"Failed to connect client socket signal.");
228 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
"Failed to connect data socket signal.");
236 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
"Failed to bind to a port for data.");
240 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
241 QString(
"Bound to port %1 for incoming data").arg(
m_dataPort));
277 uint64_t firstframe = 0;
281 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
282 QString(
"Packet doesn't start with valid Rtp Header (0x%1)")
283 .arg((uint8_t)buf[0], 0, 16));
313 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
314 QString(
"Packet type (0x%1) not handled")
322 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
323 QString(
"Received packet %1 too late, ignoring")
346 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
347 QString(
"Received required resend %1 (with ts:%2 last:%3)")
353 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
354 QString(
"Received unexpected resent packet %1")
361 auto *decoded =
new QList<AudioData>();
366 LOG(VB_PLAYBACK, LOG_ERR,
LOC + QString(
"Error decoding audio"));
378 bool first = (uint8_t)buf[0] == 0x90;
379 const char *req = buf.constData();
380 uint64_t current_ts = qFromBigEndian(*(uint32_t *)(req + 4));
381 uint64_t next_ts = qFromBigEndian(*(uint32_t *)(req + 16));
393 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Receiving %1SYNC packet")
394 .arg(first ?
"first " :
""));
398 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"SYNC: cur:%1 next:%2 time:%3")
404 std::chrono::milliseconds currentLatency = 0ms;
411 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
412 QString(
"RAOP timestamps: about to play:%1 desired:%2 latency:%3")
414 .arg(currentLatency.count()));
417 delay += currentLatency;
419 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
420 QString(
"Queue=%1 buffer=%2ms ideal=%3ms diffts:%4ms")
434 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
435 QString(
"Too much delay (%1ms), adjusting")
445 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Drop %1 packets").arg(res));
462 int16_t missed = (got < expected) ?
463 (int16_t)(((int32_t)got + UINT16_MAX + 1) - expected) :
466 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
467 QString(
"Missed %1 packet(s): expected %2 got %3 ts:%4")
468 .arg(missed).arg(expected).arg(got).arg(timestamp.count()));
472 *(
uint16_t *)(&req[4]) = qToBigEndian(expected);
473 *(
uint16_t *)(&req[6]) = qToBigEndian(missed);
477 == (qint64)req.size())
479 for (
uint16_t count = 0; count < missed; count++)
481 LOG(VB_PLAYBACK, LOG_INFO,
LOC + QString(
"Sent resend for %1")
482 .arg(expected + count));
483 m_resends.insert(expected + count, timestamp);
488 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
"Failed to send resend request.");
506 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
507 QString(
"Never received resend packet %1").arg(it.key()));
527 uint32_t ntpTicks {0};
528 auto usecs = nowAsDuration<std::chrono::microseconds>();
531 std::array<uint8_t,32> req {
537 *(uint32_t *)(&req[24]) = qToBigEndian(ntpSec);
538 *(uint32_t *)(&req[28]) = qToBigEndian(ntpTicks);
543 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
"Failed to send resend time request.");
546 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
547 QString(
"Requesting master time (Local %1.%2)")
548 .arg(ntpSec,8,16,QChar(
'0')).arg(ntpTicks,8,16,QChar(
'0')));
559 const char *req = buf.constData();
561 uint32_t ntpSec = qFromBigEndian(*(uint32_t *)(req + 8));
562 uint32_t ntpTicks = qFromBigEndian(*(uint32_t *)(req + 12));
564 auto time2 = nowAsDuration<std::chrono::milliseconds>();
565 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Read back time (Local %1.%2)")
566 .arg(ntpSec,8,16,QChar(
'0')).arg(ntpTicks,8,16,QChar(
'0')));
570 LOG(VB_AUDIO, LOG_DEBUG,
LOC + QString(
"Network Latency: %1ms")
575 uint32_t sec = qFromBigEndian(*(uint32_t *)(req + 24));
576 uint32_t ticks = qFromBigEndian(*(uint32_t *)(req + 28));
577 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Source NTP clock time %1.%2")
578 .arg(sec,8,16,QChar(
'0')).arg(ticks,8,16,QChar(
'0')));
581 std::chrono::milliseconds master =
NTPToLocal(sec, ticks);
597 return std::chrono::milliseconds((((int64_t)sec -
CLOCK_EPOCH) * 1000LL) +
598 (((int64_t)ticks * 1000LL) >> 32));
601 uint32_t &ntpSec, uint32_t &ntpTicks)
603 ntpSec = duration_cast<std::chrono::seconds>(usec).count() +
CLOCK_EPOCH;
604 ntpTicks = ((usec % 1s).count() << 32) / 1000000;
608 auto micros = durationFromTimeval<std::chrono::microseconds>(
t);
626 if ((uint8_t)buf[0] != 0x80 && (uint8_t)buf[0] != 0x90)
645 const char *ptr = buf.constData();
650 seq = qFromBigEndian(*(
uint16_t *)(ptr + 2));
651 timestamp = qFromBigEndian(*(uint32_t *)(ptr + 4));
658 const QByteArray *buf,
659 QList<AudioData> *
dest)
661 const char *data_in = buf->constData();
662 int len = buf->size();
673 int aeslen = len & ~0xf;
676 std::array<uint8_t,MAX_PACKET_SIZE> decrypted_data {};
677 EVP_CIPHER_CTX_reset(
m_cctx);
678 EVP_CIPHER_CTX_set_padding(
m_cctx, 0);
680#
if OPENSSL_VERSION_NUMBER < 0x030000000L
683 reinterpret_cast<const uint8_t *
>(
m_aesIV.data()))
686 reinterpret_cast<const uint8_t *
>(
m_aesIV.data()),
691 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
692 QString(
"EVP_DecryptInit_ex failed. (%1)")
693 .arg(ERR_error_string(ERR_get_error(),
nullptr)));
695 else if (EVP_DecryptUpdate(
m_cctx, decrypted_data.data(), &outlen1,
696 reinterpret_cast<const uint8_t *
>(data_in),
699 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
700 QString(
"EVP_DecryptUpdate failed. (%1)")
701 .arg(ERR_error_string(ERR_get_error(),
nullptr)));
703 else if (EVP_DecryptFinal_ex(
m_cctx, decrypted_data.data() + outlen1, &outlen2) != 1)
705 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
706 QString(
"EVP_DecryptFinal_ex failed. (%1)")
707 .arg(ERR_error_string(ERR_get_error(),
nullptr)));
709 std::copy(data_in + aeslen, data_in + len,
710 decrypted_data.data() + aeslen);
713 AVPacket *tmp_pkt = av_packet_alloc();
714 if (tmp_pkt ==
nullptr)
716 tmp_pkt->data = decrypted_data.data();
719 uint32_t frames_added = 0;
721 while (tmp_pkt->size > 0)
733 int num_samples = data_size /
734 (ctx->ch_layout.nb_channels * av_get_bytes_per_sample(ctx->sample_fmt));
736 frames_added += num_samples;
740 tmp_pkt->data += ret;
741 tmp_pkt->size -= ret;
743 av_packet_free(&tmp_pkt);
758 auto dtime = nowAsDuration<std::chrono::milliseconds>() -
m_timeLastSync;
779 std::chrono::milliseconds timestamp = 0ms;
785 timestamp = packet_it.key();
786 if (timestamp >= rtp)
800 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
801 QString(
"Audio discontinuity seen. Played %1 (%3) expected %2")
807 for (
const auto & data : std::as_const(*frames.
data))
817 offset = std::min(offset, data.length);
820 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
821 QString(
"ProcessAudio: Dropping %1 frames to catch up "
827 data.length - offset,
828 std::chrono::milliseconds(timestamp), framecnt);
850 if (packet_it.key() >= timestamp)
858 for (
const auto & data : std::as_const(*frames.
data))
881 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"Closing connection after inactivity.");
904 auto *socket = qobject_cast<QTcpSocket *>(sender());
908 QByteArray data = socket->readAll();
911 QByteArray printable = data.left(32);
912 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"readClient(%1): %2%3")
914 .arg(printable.toHex().toUpper().data(),
915 data.size() > 32 ?
"..." :
""));
924 QTextStream stream(data);
925 QString line = stream.readLine();
926 while (!line.isEmpty())
928 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Header(%1) = %2")
929 .arg(
m_socket->peerAddress().toString(), line));
931 if (line.contains(
"Content-Length:"))
935 line = stream.readLine();
943 int pos = stream.pos();
963 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Content(%1) = %2")
972 if (header.isEmpty())
977 if (!tags.contains(
"CSeq"))
979 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
"ProcessRequest: Didn't find CSeq");
983 QString option = header[0].left(header[0].indexOf(
" "));
988 uint64_t RTPtimestamp = 0;
989 if (tags.contains(
"RTP-Info"))
992 QString data = tags[
"RTP-Info"];
993 QStringList items = data.split(
";");
994 for (
const QString& item : std::as_const(items))
996 if (item.startsWith(
"seq"))
998 RTPseq = item.mid(item.indexOf(
"=") + 1).trimmed().toUShort();
1000 else if (item.startsWith(
"rtptime"))
1002 RTPtimestamp = item.mid(item.indexOf(
"=") + 1).trimmed().toUInt();
1005 LOG(VB_PLAYBACK, LOG_INFO,
LOC + QString(
"RTP-Info: seq=%1 rtptime=%2")
1006 .arg(RTPseq).arg(RTPtimestamp));
1015 if (!tags.contains(
"Authorization"))
1028 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"RAOP client authenticated");
1032 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"RAOP authentication failed");
1039 if (tags.contains(
"Apple-Challenge"))
1041 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Received Apple-Challenge"));
1047 std::vector<uint8_t>to;
1050 QByteArray challenge =
1051 QByteArray::fromBase64(tags[
"Apple-Challenge"].toLatin1());
1052 int challenge_size = challenge.size();
1053 if (challenge_size != 16)
1055 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1056 QString(
"Base64 decoded challenge size %1, expected 16")
1057 .arg(challenge_size));
1058 challenge_size = std::min(challenge_size, 16);
1062 std::array<uint8_t,38> from {};
1063 std::copy(challenge.cbegin(), challenge.cbegin() + challenge_size,
1065 i += challenge_size;
1066 if (
m_socket->localAddress().protocol() ==
1067 QAbstractSocket::IPv4Protocol)
1069 uint32_t ip =
m_socket->localAddress().toIPv4Address();
1070 ip = qToBigEndian(ip);
1071 memcpy(&from[i], &ip, 4);
1074 else if (
m_socket->localAddress().protocol() ==
1075 QAbstractSocket::IPv6Protocol)
1077 Q_IPV6ADDR ip =
m_socket->localAddress().toIPv6Address();
1079 "\x00\x00\x00\x00" "\x00\x00\x00\x00" "\x00\x00\xff\xff",
1082 memcpy(&from[i], &ip[12], 4);
1087 memcpy(&from[i], &ip, 16);
1098 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1099 QString(
"Hardware MAC address size %1, expected %2")
1106 memset(&from[i], 0, pad);
1110 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1111 QString(
"Full base64 response: '%1' size %2")
1112 .arg(QByteArray((
char *)from.data(), i).toBase64().constData())
1116 if (
nullptr == pctx)
1118 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
1119 QString(
"Cannot create ENV_PKEY_CTX from key. (%1)")
1120 .arg(ERR_error_string(ERR_get_error(),
nullptr)));
1122 else if (EVP_PKEY_sign_init(pctx) <= 0)
1124 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
1125 QString(
"EVP_PKEY_sign_init failed. (%1)")
1126 .arg(ERR_error_string(ERR_get_error(),
nullptr)));
1128 else if (EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PADDING) <= 0)
1130 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
1131 QString(
"Cannot set RSA_PKCS1_PADDING on context. (%1)")
1132 .arg(ERR_error_string(ERR_get_error(),
nullptr)));
1134 else if (EVP_PKEY_sign(pctx, to.data(), &tosize,
1135 from.data(), i) <= 0)
1137 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
1138 QString(
"EVP_PKEY_sign failed. (%1)")
1139 .arg(ERR_error_string(ERR_get_error(),
nullptr)));
1142 QByteArray base64 = QByteArray((
const char *)to.data(), tosize).toBase64();
1144 for (
int pos = base64.size() - 1; pos > 0; pos--)
1146 if (base64[pos] ==
'=')
1151 LOG(VB_PLAYBACK, LOG_DEBUG, QString(
"tSize=%1 tLen=%2 tResponse=%3")
1152 .arg(tosize).arg(base64.size()).arg(base64.constData()));
1156 QString responseData;
1157 if (option ==
"OPTIONS")
1159 *
m_textStream <<
"Public: ANNOUNCE, SETUP, RECORD, PAUSE, FLUSH, "
1160 "TEARDOWN, OPTIONS, GET_PARAMETER, SET_PARAMETER, POST, GET\r\n";
1162 else if (option ==
"ANNOUNCE")
1165 for (
const QString& line : std::as_const(lines))
1167 if (line.startsWith(
"a=rsaaeskey:"))
1169 QString key = line.mid(12).trimmed();
1170 QByteArray decodedkey = QByteArray::fromBase64(key.toLatin1());
1171 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1172 QString(
"RSAAESKey: %1 (base64 decoded size %2)")
1173 .arg(key).arg(decodedkey.size()));
1181 size_t size_out {size};
1182 if (
nullptr == pctx)
1184 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
1185 QString(
"Cannot create ENV_PKEY_CTX from key. (%1)")
1186 .arg(ERR_error_string(ERR_get_error(),
nullptr)));
1188 else if (EVP_PKEY_decrypt_init(pctx) <= 0)
1190 LOG(VB_PLAYBACK, LOG_WARNING,
LOC + QString(
"EVP_PKEY_decrypt_init failed. (%1)")
1191 .arg(ERR_error_string(ERR_get_error(),
nullptr)));
1193 else if (EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_OAEP_PADDING) <= 0)
1195 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
1196 QString(
"Cannot set RSA_PKCS1_OAEP_PADDING on context. (%1)")
1197 .arg(ERR_error_string(ERR_get_error(),
nullptr)));
1199 else if (EVP_PKEY_decrypt(pctx,
m_sessionKey.data(), &size_out,
1200 (
const unsigned char *)decodedkey.constData(), decodedkey.size()) > 0)
1203 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1204 "Successfully decrypted AES key from RSA.");
1208 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
1209 QString(
"Failed to decrypt AES key from RSA. (%1)")
1210 .arg(ERR_error_string(ERR_get_error(),
nullptr)));
1214 else if (line.startsWith(
"a=aesiv:"))
1216 QString aesiv = line.mid(8).trimmed();
1217 m_aesIV = QByteArray::fromBase64(aesiv.toLatin1());
1218 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1219 QString(
"AESIV: %1 (base64 decoded size %2)")
1220 .arg(aesiv).arg(
m_aesIV.size()));
1222 else if (line.startsWith(
"a=fmtp:"))
1225 QString format = line.mid(7).trimmed();
1226 QList<QString>
formats = format.split(
' ');
1227 for (
const QString& fmt : std::as_const(
formats))
1231 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1232 QString(
"Audio parameter: %1").arg(fmt));
1240 else if (option ==
"SETUP")
1242 if (tags.contains(
"Transport"))
1245 auto *dev = qobject_cast<MythRAOPDevice*>(parent());
1247 dev->DeleteAllClients(
this);
1251 int control_port = 0;
1252 int timing_port = 0;
1253 QString data = tags[
"Transport"];
1254 QStringList items = data.split(
";");
1255 bool events =
false;
1257 for (
const QString& item : std::as_const(items))
1259 if (item.startsWith(
"control_port"))
1260 control_port = item.mid(item.indexOf(
"=") + 1).trimmed().toUInt();
1261 else if (item.startsWith(
"timing_port"))
1262 timing_port = item.mid(item.indexOf(
"=") + 1).trimmed().toUInt();
1263 else if (item.startsWith(
"events"))
1267 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
1268 QString(
"Negotiated setup with client %1 on port %2")
1269 .arg(
m_socket->peerAddress().toString())
1271 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1272 QString(
"control port: %1 timing port: %2")
1273 .arg(control_port).arg(timing_port));
1285 int controlbind_port =
1288 if (controlbind_port < 0)
1290 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1291 QString(
"Failed to bind to client control port. "
1292 "Control of audio stream may fail"));
1296 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
1297 QString(
"Bound to client control port %1 on port %2")
1298 .arg(control_port).arg(controlbind_port));
1314 int timingbind_port =
1317 if (timingbind_port < 0)
1319 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1320 QString(
"Failed to bind to client timing port. "
1321 "Timing of audio stream will be incorrect"));
1325 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
1326 QString(
"Bound to client timing port %1 on port %2")
1327 .arg(timing_port).arg(timingbind_port));
1340 client->disconnect();
1358 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1359 "Failed to find a port for RAOP events server.");
1363 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
1364 QString(
"Listening for RAOP events on port %1").arg(
m_eventPort));
1379 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1380 QString(
"Audio hardware latency: %1ms")
1388 for (
const QString& item : std::as_const(items))
1394 if (item.startsWith(
"control_port"))
1396 newdata +=
"control_port=" + QString::number(controlbind_port);
1398 else if (item.startsWith(
"timing_port"))
1400 newdata +=
"timing_port=" + QString::number(timingbind_port);
1402 else if (item.startsWith(
"events"))
1404 newdata +=
"event_port=" + QString::number(
m_eventPort);
1416 newdata +=
"server_port=" + QString::number(
m_dataPort);
1427 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1428 "No Transport details found - Ignoring");
1431 else if (option ==
"RECORD")
1446 else if (option ==
"FLUSH")
1461 else if (option ==
"SET_PARAMETER")
1463 if (tags.contains(
"Content-Type"))
1465 if (tags[
"Content-Type"] ==
"text/parameters")
1470 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1471 QString(
"text/parameters: name=%1 parem=%2")
1476 float volume = (param.toFloat() + 30.0F) * 100.0F / 30.0F;
1479 LOG(VB_PLAYBACK, LOG_INFO,
1480 LOC + QString(
"Setting volume to %1 (raw %3)")
1481 .arg(volume).arg(param));
1484 else if (name ==
"progress")
1486 QStringList items = param.split(
"/");
1487 if (items.size() == 3)
1498 LOG(VB_PLAYBACK, LOG_INFO,
1499 LOC +QString(
"Progress: %1/%2")
1505 else if(tags[
"Content-Type"] ==
"image/none")
1510 else if(tags[
"Content-Type"].startsWith(
"image/"))
1516 else if (tags[
"Content-Type"] ==
"application/x-dmap-tagged")
1520 LOG(VB_PLAYBACK, LOG_INFO,
1521 QString(
"Receiving Title:%1 Artist:%2 Album:%3 Format:%4")
1528 else if (option ==
"GET_PARAMETER")
1530 if (tags.contains(
"Content-Type"))
1532 if (tags[
"Content-Type"] ==
"text/parameters")
1536 for (
const QString& line : std::as_const(lines))
1538 if (line ==
"volume")
1542 responseData += QString(
"volume: %1\r\n")
1543 .arg((volume * 30.0F / 100.0F) - 30.0F,1,
'f',6,
'0');
1549 else if (option ==
"TEARDOWN")
1556 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Command not handled: %1")
1568 *stream <<
"RTSP/1.0 401 Unauthorised\r\n";
1569 *stream <<
"Server: AirTunes/130.14\r\n";
1570 *stream <<
"WWW-Authenticate: Digest realm=\"raop\", ";
1571 *stream <<
"nonce=\"" +
m_nonce +
"\"\r\n";
1572 *stream <<
"CSeq: " << cseq <<
"\r\n";
1575 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1576 QString(
"Finished Authentication request %2, Send: %3")
1577 .arg(cseq).arg(socket->flush()));
1581 QString &option, QString &cseq, QString &responseData)
1585 *stream <<
"Server: AirTunes/130.14\r\n";
1586 *stream <<
"CSeq: " << cseq <<
"\r\n";
1587 if (!responseData.isEmpty())
1588 *stream <<
"Content-Length: " << QString::number(responseData.length()) <<
"\r\n\r\n" << responseData;
1592 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Finished %1 %2 , Send: %3")
1593 .arg(option, cseq, QString::number(socket->flush())));
1603 static QMutex s_lock;
1604 QMutexLocker locker(&s_lock);
1609 QString sName(
"/RAOPKey.rsa" );
1611 FILE *
file = fopen(sPath.toUtf8().constData(),
"rb");
1626 int type = EVP_PKEY_type(
id);
1627 if (
type != EVP_PKEY_RSA)
1636 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
"Loaded RSA private key");
1648 if (lines.isEmpty())
1651 for (
const QString& line : std::as_const(lines))
1653 int index = line.indexOf(
":");
1656 result.insert(line.left(index).trimmed(),
1657 line.mid(index + 1).trimmed());
1666 QTextStream stream(lines);
1668 QString line = stream.readLine();
1669 while (!line.isEmpty())
1672 line = stream.readLine();
1687 QTime time = QTime(0,0).addSecs(timeInSeconds);
1688 return time.toString(time.hour() > 0 ?
"HH:mm:ss" :
"mm:ss");
1698 return std::chrono::milliseconds((frames * 1000ULL) /
m_frameRate);
1715 QMap<QString,QString> result;
1717 while (offset < dmap.size())
1719 QString tag = dmap.mid(offset, 4);
1721 uint32_t length = qFromBigEndian(*(uint32_t *)(dmap.constData() + offset));
1722 offset +=
sizeof(uint32_t);
1723 QString
content = QString::fromUtf8(dmap.mid(offset,
1724 length).constData());
1736 m_codec = avcodec_find_decoder(AV_CODEC_ID_ALAC);
1739 LOG(VB_PLAYBACK, LOG_ERR,
LOC
1740 +
"Failed to create ALAC decoder- going silent...");
1747 auto *extradata =
new unsigned char[36];
1748 memset(extradata, 0, 36);
1751 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1752 "Creating decoder but haven't seen audio format.");
1757 extradata[12] = (fs >> 24) & 0xff;
1758 extradata[13] = (fs >> 16) & 0xff;
1759 extradata[14] = (fs >> 8) & 0xff;
1760 extradata[15] = fs & 0xff;
1772 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1773 "Failed to open ALAC decoder - going silent...");
1777 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
"Opened ALAC decoder.");
1806 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1807 "Failed to open audio device. Going silent...");
1814 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
"Opened audio device.");
1862 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"AudioCardLatency: ts=%1ms")
1863 .arg(audiots.count()));
1869 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
1870 QString(
"New connection from %1:%2 for RAOP events server.")
1871 .arg(client->peerAddress().toString()).arg(client->peerPort()));
1879 auto *client = qobject_cast<QTcpSocket *>(sender());
1882 if (client !=
nullptr)
1883 label = QString(
"%1:%2").arg(client->peerAddress().toString()).arg(client->peerPort());
1885 label = QString(
"unknown");
1887 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1888 QString(
"%1 disconnected from RAOP events server.").arg(label));
1894 auto duration = std::chrono::seconds(
1904 image,
m_dmap, duration, position);
1909 duration, position);
static const std::array< const std::string, 8 > formats
bool isConfigured() const
virtual bool AddData(void *buffer, int len, std::chrono::milliseconds timecode, int frames)=0
Add data to the audiobuffer for playback.
virtual bool IsPaused(void) const =0
static AudioOutput * OpenAudio(const QString &main_device, const QString &passthru_device, AudioFormat format, int channels, AVCodecID codec, int samplerate, AudioOutputSource source, bool set_initial_vol, bool passthru, int upmixer_startup=0, AudioOutputSettings *custom=nullptr)
virtual std::chrono::milliseconds GetAudioBufferedTime(void)
report amount of audio buffered in milliseconds.
virtual std::chrono::milliseconds GetAudiotime(void)=0
virtual std::chrono::milliseconds LengthLastData(void) const
virtual int GetBytesPerFrame(void) const
static const int kMaxSizeBuffer
kMaxSizeBuffer is the maximum size of a buffer to be used with DecodeAudio
int DecodeAudio(AVCodecContext *ctx, uint8_t *buffer, int &data_size, const AVPacket *pkt)
Utility routine.
virtual void Reset(void)=0
virtual void Pause(bool paused)=0
void emitTVPlaybackStopped(void)
QString GetSetting(const QString &key, const QString &defaultval="")
void WantingPlayback(QObject *sender)
All the objects that have registered using MythCoreContext::RegisterForPlayback but sender will be ca...
bool GetBoolSetting(const QString &key, bool defaultval=false)
bool Queue(const MythNotification ¬ification)
Queue a notification Queue() is thread-safe and can be called from anywhere.
void SetId(int Id)
Contains the application registration id.
static const Type kUpdate
void SetFullScreen(bool FullScreen)
A notification may request to be displayed in full screen, this request may not be fullfilled should ...
void SetParent(void *Parent)
Contains the parent address. Required if id is set Id provided must match the parent address as provi...
void SetDuration(std::chrono::seconds Duration)
Contains a duration during which the notification will be displayed for. The duration is informative ...
ServerPool * m_dataSocket
QList< int > m_audioFormat
static QString stringFromSeconds(int timeInSeconds)
stringFromSeconds:
bool OpenAudioDevice(void)
ServerPool * m_eventServer
bool m_allowVolumeControl
QHostAddress m_peerAddress
QMap< uint16_t, std::chrono::milliseconds > m_resends
void newEventClient(QTcpSocket *client)
void SendNotification(bool update=false)
void CloseAudioDevice(void)
static void microsecondsToNTP(std::chrono::microseconds usec, uint32_t &ntpSec, uint32_t &ntpTicks)
std::chrono::milliseconds framesToMs(uint64_t frames) const
framesDuration Description: return the duration in ms of frames
void ProcessRequest(const QStringList &header, const QByteArray &content)
void DestroyDecoder(void)
void StopAudioTimer(void)
static QMap< QString, QString > decodeDMAP(const QByteArray &dmap)
decodeDMAP:
std::chrono::milliseconds m_bufferLength
uint64_t MsToFrame(std::chrono::milliseconds millis) const
std::chrono::milliseconds m_currentTimestamp
void SendResendRequest(std::chrono::milliseconds timestamp, uint16_t expected, uint16_t got)
SendResendRequest: Request RAOP client to resend missed RTP packets.
void ProcessTimeResponse(const QByteArray &buf)
ProcessTimeResponse: Calculate the network latency, we do not use the reference time send by itunes i...
static EVP_PKEY * g_devPrivKey
RaopNetStream * m_textStream
QTimer * m_dequeueAudioTimer
int ExpireAudio(std::chrono::milliseconds timestamp)
const EVP_CIPHER * m_cipher
QStringList m_incomingHeaders
static void FinishResponse(RaopNetStream *stream, QTcpSocket *socket, QString &option, QString &cseq, QString &responseData)
void ProcessSync(const QByteArray &buf)
static QStringList splitLines(const QByteArray &lines)
void readClient(void)
readClient: signal handler for RAOP client connection Handle initialisation of session
static bool GetPacketType(const QByteArray &buf, uint8_t &type, uint16_t &seq, uint64_t ×tamp)
void udpDataReady(QByteArray buf, const QHostAddress &peer, quint16 port)
Socket incoming data signal handler use for audio, control and timing socket.
QMap< std::chrono::milliseconds, AudioPacket > m_audioQueue
uint32_t decodeAudioPacket(uint8_t type, const QByteArray *buf, QList< AudioData > *dest)
MythRAOPConnection(QObject *parent, QTcpSocket *socket, QByteArray id, int port)
QList< QTcpSocket * > m_eventClients
ServerPool * m_clientControlSocket
std::chrono::milliseconds m_networkLatency
QByteArray m_incomingContent
uint32_t m_progressCurrent
std::chrono::milliseconds m_cardLatency
static std::chrono::milliseconds NTPToLocal(uint32_t sec, uint32_t ticks)
~MythRAOPConnection() override
static bool LoadKey(void)
LoadKey.
AVCodecContext * m_codecContext
std::chrono::milliseconds m_adjustedLatency
ServerPool * m_clientTimingSocket
std::chrono::milliseconds m_nextTimestamp
std::vector< uint8_t > m_sessionKey
static RawHash FindTags(const QStringList &lines)
std::chrono::milliseconds m_timeLastSync
std::chrono::milliseconds AudioCardLatency(void)
AudioCardLatency: Description: Play silence and calculate audio latency between input / output.
std::chrono::milliseconds m_clockSkew
std::chrono::milliseconds m_lastTimestamp
void StartAudioTimer(void)
void ExpireResendRequests(std::chrono::milliseconds timestamp)
ExpireResendRequests: Expire resend requests that are older than timestamp.
static QString g_rsaLastError
static void timevalToNTP(timeval t, uint32_t &ntpSec, uint32_t &ntpTicks)
void FinishAuthenticationResponse(RaopNetStream *stream, QTcpSocket *socket, QString &cseq)
void SendTimeRequest(void)
SendTimeRequest: Send a time request to the RAOP client.
RaopNetStream & operator<<(const QString &str)
RaopNetStream(QIODevice *device)
Manages a collection of sockets listening on different ports.
void newDatagram(QByteArray, QHostAddress, quint16)
int tryListeningPort(int baseport, int range=1)
tryListeningPort
int tryBindingPort(int baseport, int range=1)
tryBindingPort
void newConnection(QTcpSocket *)
qint64 writeDatagram(const char *data, qint64 size, const QHostAddress &addr, quint16 port)
virtual void SetCurrentVolume(int value)
virtual uint GetCurrentVolume(void) const
static const std::array< const uint64_t, 4 > samples
QByteArray DigestMd5Response(const QString &response, const QString &option, const QString &nonce, const QString &password, QByteArray &auth)
QString GenerateNonce(void)
static constexpr size_t AIRPLAY_HARDWARE_ID_SIZE
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
MythNotificationCenter * GetNotificationCenter(void)
static constexpr std::chrono::milliseconds AUDIO_BUFFER
static constexpr uint8_t TIMING_REQUEST
#define EVP_PKEY_get_size
static constexpr uint8_t RANGE_RESEND
static constexpr uint8_t AUDIO_RESEND
static constexpr std::chrono::milliseconds AUDIOCARD_BUFFER
static constexpr size_t MAX_PACKET_SIZE
static constexpr uint8_t SYNC
static constexpr uint8_t AUDIO_DATA
static constexpr uint8_t FIRSTSYNC
static constexpr uint64_t CLOCK_EPOCH
static constexpr uint8_t FIRSTAUDIO_DATA
static constexpr uint8_t TIMING_RESPONSE
QHash< QString, QString > RawHash
static constexpr int RAOP_PORT_RANGE
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
MBASE_PUBLIC long long copy(QFile &dst, QFile &src, uint block_size=0)
Copies src file to dst file.
QList< AudioData > * data