11#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
12#include <QStringConverter>
31#if OPENSSL_VERSION_NUMBER < 0x030000000L
32#define EVP_PKEY_get_id EVP_PKEY_id
33#define EVP_PKEY_get_size EVP_PKEY_size
36#define LOC QString("RAOP Conn: ")
45static constexpr uint8_t
SYNC { 0x54 };
67 LOG(VB_PLAYBACK, LOG_DEBUG,
68 LOC + QString(
"Sending(%1): ").arg(str.length()) + str.trimmed());
69 QTextStream *q =
this;
76 QByteArray
id,
int port)
79 m_hardwareId(
std::move(id)),
81 m_cctx(EVP_CIPHER_CTX_new()),
84#if OPENSSL_VERSION_NUMBER < 0x030000000L
88 m_cipher = EVP_CIPHER_fetch(
nullptr,
"AES-128-CBC",
nullptr);
99 client->deleteLater();
130 nc->UnRegister(
this,
m_id);
133 EVP_CIPHER_CTX_free(
m_cctx);
135#if OPENSSL_VERSION_NUMBER >= 0x030000000L
206#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
213 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
"Failed to connect client socket signal.");
222 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
"Failed to connect data socket signal.");
230 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
"Failed to bind to a port for data.");
234 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
235 QString(
"Bound to port %1 for incoming data").arg(
m_dataPort));
271 uint64_t firstframe = 0;
275 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
276 QString(
"Packet doesn't start with valid Rtp Header (0x%1)")
277 .arg((uint8_t)buf[0], 0, 16));
307 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
308 QString(
"Packet type (0x%1) not handled")
316 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
317 QString(
"Received packet %1 too late, ignoring")
340 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
341 QString(
"Received required resend %1 (with ts:%2 last:%3)")
347 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
348 QString(
"Received unexpected resent packet %1")
355 auto *decoded =
new QList<AudioData>();
360 LOG(VB_PLAYBACK, LOG_ERR,
LOC + QString(
"Error decoding audio"));
372 bool first = (uint8_t)buf[0] == 0x90;
373 const char *req = buf.constData();
374 uint64_t current_ts = qFromBigEndian(*(uint32_t *)(req + 4));
375 uint64_t next_ts = qFromBigEndian(*(uint32_t *)(req + 16));
387 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Receiving %1SYNC packet")
388 .arg(first ?
"first " :
""));
392 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"SYNC: cur:%1 next:%2 time:%3")
398 std::chrono::milliseconds currentLatency = 0ms;
405 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
406 QString(
"RAOP timestamps: about to play:%1 desired:%2 latency:%3")
408 .arg(currentLatency.count()));
411 delay += currentLatency;
413 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
414 QString(
"Queue=%1 buffer=%2ms ideal=%3ms diffts:%4ms")
428 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
429 QString(
"Too much delay (%1ms), adjusting")
439 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Drop %1 packets").arg(res));
456 int16_t missed = (got < expected) ?
457 (int16_t)(((int32_t)got + UINT16_MAX + 1) - expected) :
460 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
461 QString(
"Missed %1 packet(s): expected %2 got %3 ts:%4")
462 .arg(missed).arg(expected).arg(got).arg(timestamp.count()));
466 *(
uint16_t *)(&req[4]) = qToBigEndian(expected);
467 *(
uint16_t *)(&req[6]) = qToBigEndian(missed);
471 == (qint64)req.size())
473 for (
uint16_t count = 0; count < missed; count++)
475 LOG(VB_PLAYBACK, LOG_INFO,
LOC + QString(
"Sent resend for %1")
476 .arg(expected + count));
477 m_resends.insert(expected + count, timestamp);
482 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
"Failed to send resend request.");
496 QMutableMapIterator<uint16_t,std::chrono::milliseconds> it(
m_resends);
502 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
503 QString(
"Never received resend packet %1").arg(it.key()));
519 uint32_t ntpTicks {0};
520 auto usecs = nowAsDuration<std::chrono::microseconds>();
523 std::array<uint8_t,32> req {
529 *(uint32_t *)(&req[24]) = qToBigEndian(ntpSec);
530 *(uint32_t *)(&req[28]) = qToBigEndian(ntpTicks);
535 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
"Failed to send resend time request.");
538 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
539 QString(
"Requesting master time (Local %1.%2)")
540 .arg(ntpSec,8,16,QChar(
'0')).arg(ntpTicks,8,16,QChar(
'0')));
551 const char *req = buf.constData();
553 uint32_t ntpSec = qFromBigEndian(*(uint32_t *)(req + 8));
554 uint32_t ntpTicks = qFromBigEndian(*(uint32_t *)(req + 12));
556 auto time2 = nowAsDuration<std::chrono::milliseconds>();
557 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Read back time (Local %1.%2)")
558 .arg(ntpSec,8,16,QChar(
'0')).arg(ntpTicks,8,16,QChar(
'0')));
562 LOG(VB_AUDIO, LOG_DEBUG,
LOC + QString(
"Network Latency: %1ms")
567 uint32_t sec = qFromBigEndian(*(uint32_t *)(req + 24));
568 uint32_t ticks = qFromBigEndian(*(uint32_t *)(req + 28));
569 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Source NTP clock time %1.%2")
570 .arg(sec,8,16,QChar(
'0')).arg(ticks,8,16,QChar(
'0')));
573 std::chrono::milliseconds master =
NTPToLocal(sec, ticks);
589 return std::chrono::milliseconds((((int64_t)sec -
CLOCK_EPOCH) * 1000LL) +
590 (((int64_t)ticks * 1000LL) >> 32));
593 uint32_t &ntpSec, uint32_t &ntpTicks)
595 ntpSec = duration_cast<std::chrono::seconds>(usec).count() +
CLOCK_EPOCH;
596 ntpTicks = ((usec % 1s).count() << 32) / 1000000;
600 auto micros = durationFromTimeval<std::chrono::microseconds>(
t);
618 if ((uint8_t)buf[0] != 0x80 && (uint8_t)buf[0] != 0x90)
637 const char *ptr = buf.constData();
642 seq = qFromBigEndian(*(
uint16_t *)(ptr + 2));
643 timestamp = qFromBigEndian(*(uint32_t *)(ptr + 4));
650 const QByteArray *buf,
651 QList<AudioData> *
dest)
653 const char *data_in = buf->constData();
654 int len = buf->size();
665 int aeslen = len & ~0xf;
668 std::array<uint8_t,MAX_PACKET_SIZE> decrypted_data {};
669 EVP_CIPHER_CTX_reset(
m_cctx);
670 EVP_CIPHER_CTX_set_padding(
m_cctx, 0);
672#
if OPENSSL_VERSION_NUMBER < 0x030000000L
675 reinterpret_cast<const uint8_t *
>(
m_aesIV.data()))
678 reinterpret_cast<const uint8_t *
>(
m_aesIV.data()),
683 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
684 QString(
"EVP_DecryptInit_ex failed. (%1)")
685 .arg(ERR_error_string(ERR_get_error(),
nullptr)));
687 else if (EVP_DecryptUpdate(
m_cctx, decrypted_data.data(), &outlen1,
688 reinterpret_cast<const uint8_t *
>(data_in),
691 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
692 QString(
"EVP_DecryptUpdate failed. (%1)")
693 .arg(ERR_error_string(ERR_get_error(),
nullptr)));
695 else if (EVP_DecryptFinal_ex(
m_cctx, decrypted_data.data() + outlen1, &outlen2) != 1)
697 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
698 QString(
"EVP_DecryptFinal_ex failed. (%1)")
699 .arg(ERR_error_string(ERR_get_error(),
nullptr)));
701 std::copy(data_in + aeslen, data_in + len,
702 decrypted_data.data() + aeslen);
705 AVPacket *tmp_pkt = av_packet_alloc();
706 if (tmp_pkt ==
nullptr)
708 tmp_pkt->data = decrypted_data.data();
711 uint32_t frames_added = 0;
713 while (tmp_pkt->size > 0)
725 int num_samples = data_size /
726 (ctx->ch_layout.nb_channels * av_get_bytes_per_sample(ctx->sample_fmt));
728 frames_added += num_samples;
732 tmp_pkt->data += ret;
733 tmp_pkt->size -= ret;
735 av_packet_free(&tmp_pkt);
750 auto dtime = nowAsDuration<std::chrono::milliseconds>() -
m_timeLastSync;
771 std::chrono::milliseconds timestamp = 0ms;
773 QMapIterator<std::chrono::milliseconds,AudioPacket> packet_it(
m_audioQueue);
774 while (packet_it.hasNext() && i <= max_packets)
778 timestamp = packet_it.key();
789 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
790 QString(
"Audio discontinuity seen. Played %1 (%3) expected %2")
796 for (
const auto & data : std::as_const(*frames.
data))
806 offset = std::min(offset, data.length);
809 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
810 QString(
"ProcessAudio: Dropping %1 frames to catch up "
816 data.length - offset,
817 std::chrono::milliseconds(timestamp), framecnt);
841 QMutableMapIterator<std::chrono::milliseconds,AudioPacket> packet_it(
m_audioQueue);
842 while (packet_it.hasNext())
845 if (packet_it.key() < timestamp)
850 for (
const auto & data : std::as_const(*frames.
data))
874 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"Closing connection after inactivity.");
897 auto *socket = qobject_cast<QTcpSocket *>(sender());
901 QByteArray data = socket->readAll();
904 QByteArray printable = data.left(32);
905 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"readClient(%1): %2%3")
907 .arg(printable.toHex().toUpper().data(),
908 data.size() > 32 ?
"..." :
""));
917 QTextStream stream(data);
918 QString line = stream.readLine();
919 while (!line.isEmpty())
921 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Header(%1) = %2")
922 .arg(
m_socket->peerAddress().toString(), line));
924 if (line.contains(
"Content-Length:"))
928 line = stream.readLine();
936 int pos = stream.pos();
956 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Content(%1) = %2")
965 if (header.isEmpty())
970 if (!tags.contains(
"CSeq"))
972 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
"ProcessRequest: Didn't find CSeq");
976 QString option = header[0].left(header[0].indexOf(
" "));
981 uint64_t RTPtimestamp = 0;
982 if (tags.contains(
"RTP-Info"))
985 QString data = tags[
"RTP-Info"];
986 QStringList items = data.split(
";");
987 for (
const QString& item : std::as_const(items))
989 if (item.startsWith(
"seq"))
991 RTPseq = item.mid(item.indexOf(
"=") + 1).trimmed().toUShort();
993 else if (item.startsWith(
"rtptime"))
995 RTPtimestamp = item.mid(item.indexOf(
"=") + 1).trimmed().toUInt();
998 LOG(VB_PLAYBACK, LOG_INFO,
LOC + QString(
"RTP-Info: seq=%1 rtptime=%2")
999 .arg(RTPseq).arg(RTPtimestamp));
1008 if (!tags.contains(
"Authorization"))
1021 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"RAOP client authenticated");
1025 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"RAOP authentication failed");
1032 if (tags.contains(
"Apple-Challenge"))
1034 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Received Apple-Challenge"));
1040 std::vector<uint8_t>to;
1043 QByteArray challenge =
1044 QByteArray::fromBase64(tags[
"Apple-Challenge"].toLatin1());
1045 int challenge_size = challenge.size();
1046 if (challenge_size != 16)
1048 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1049 QString(
"Base64 decoded challenge size %1, expected 16")
1050 .arg(challenge_size));
1051 challenge_size = std::min(challenge_size, 16);
1055 std::array<uint8_t,38> from {};
1056 std::copy(challenge.cbegin(), challenge.cbegin() + challenge_size,
1058 i += challenge_size;
1059 if (
m_socket->localAddress().protocol() ==
1060 QAbstractSocket::IPv4Protocol)
1062 uint32_t ip =
m_socket->localAddress().toIPv4Address();
1063 ip = qToBigEndian(ip);
1064 memcpy(&from[i], &ip, 4);
1067 else if (
m_socket->localAddress().protocol() ==
1068 QAbstractSocket::IPv6Protocol)
1070 Q_IPV6ADDR ip =
m_socket->localAddress().toIPv6Address();
1072 "\x00\x00\x00\x00" "\x00\x00\x00\x00" "\x00\x00\xff\xff",
1075 memcpy(&from[i], &ip[12], 4);
1080 memcpy(&from[i], &ip, 16);
1091 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1092 QString(
"Hardware MAC address size %1, expected %2")
1099 memset(&from[i], 0, pad);
1103 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1104 QString(
"Full base64 response: '%1' size %2")
1105 .arg(QByteArray((
char *)from.data(), i).toBase64().constData())
1109 if (
nullptr == pctx)
1111 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
1112 QString(
"Cannot create ENV_PKEY_CTX from key. (%1)")
1113 .arg(ERR_error_string(ERR_get_error(),
nullptr)));
1115 else if (EVP_PKEY_sign_init(pctx) <= 0)
1117 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
1118 QString(
"EVP_PKEY_sign_init failed. (%1)")
1119 .arg(ERR_error_string(ERR_get_error(),
nullptr)));
1121 else if (EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PADDING) <= 0)
1123 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
1124 QString(
"Cannot set RSA_PKCS1_PADDING on context. (%1)")
1125 .arg(ERR_error_string(ERR_get_error(),
nullptr)));
1127 else if (EVP_PKEY_sign(pctx, to.data(), &tosize,
1128 from.data(), i) <= 0)
1130 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
1131 QString(
"EVP_PKEY_sign failed. (%1)")
1132 .arg(ERR_error_string(ERR_get_error(),
nullptr)));
1135 QByteArray base64 = QByteArray((
const char *)to.data(), tosize).toBase64();
1137 for (
int pos = base64.size() - 1; pos > 0; pos--)
1139 if (base64[pos] ==
'=')
1144 LOG(VB_PLAYBACK, LOG_DEBUG, QString(
"tSize=%1 tLen=%2 tResponse=%3")
1145 .arg(tosize).arg(base64.size()).arg(base64.constData()));
1149 QString responseData;
1150 if (option ==
"OPTIONS")
1152 *
m_textStream <<
"Public: ANNOUNCE, SETUP, RECORD, PAUSE, FLUSH, "
1153 "TEARDOWN, OPTIONS, GET_PARAMETER, SET_PARAMETER, POST, GET\r\n";
1155 else if (option ==
"ANNOUNCE")
1158 for (
const QString& line : std::as_const(lines))
1160 if (line.startsWith(
"a=rsaaeskey:"))
1162 QString key = line.mid(12).trimmed();
1163 QByteArray decodedkey = QByteArray::fromBase64(key.toLatin1());
1164 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1165 QString(
"RSAAESKey: %1 (base64 decoded size %2)")
1166 .arg(key).arg(decodedkey.size()));
1174 size_t size_out {size};
1175 if (
nullptr == pctx)
1177 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
1178 QString(
"Cannot create ENV_PKEY_CTX from key. (%1)")
1179 .arg(ERR_error_string(ERR_get_error(),
nullptr)));
1181 else if (EVP_PKEY_decrypt_init(pctx) <= 0)
1183 LOG(VB_PLAYBACK, LOG_WARNING,
LOC + QString(
"EVP_PKEY_decrypt_init failed. (%1)")
1184 .arg(ERR_error_string(ERR_get_error(),
nullptr)));
1186 else if (EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_OAEP_PADDING) <= 0)
1188 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
1189 QString(
"Cannot set RSA_PKCS1_OAEP_PADDING on context. (%1)")
1190 .arg(ERR_error_string(ERR_get_error(),
nullptr)));
1192 else if (EVP_PKEY_decrypt(pctx,
m_sessionKey.data(), &size_out,
1193 (
const unsigned char *)decodedkey.constData(), decodedkey.size()) > 0)
1196 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1197 "Successfully decrypted AES key from RSA.");
1201 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
1202 QString(
"Failed to decrypt AES key from RSA. (%1)")
1203 .arg(ERR_error_string(ERR_get_error(),
nullptr)));
1207 else if (line.startsWith(
"a=aesiv:"))
1209 QString aesiv = line.mid(8).trimmed();
1210 m_aesIV = QByteArray::fromBase64(aesiv.toLatin1());
1211 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1212 QString(
"AESIV: %1 (base64 decoded size %2)")
1213 .arg(aesiv).arg(
m_aesIV.size()));
1215 else if (line.startsWith(
"a=fmtp:"))
1218 QString format = line.mid(7).trimmed();
1219 QList<QString>
formats = format.split(
' ');
1220 for (
const QString& fmt : std::as_const(
formats))
1224 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1225 QString(
"Audio parameter: %1").arg(fmt));
1233 else if (option ==
"SETUP")
1235 if (tags.contains(
"Transport"))
1238 auto *dev = qobject_cast<MythRAOPDevice*>(parent());
1240 dev->DeleteAllClients(
this);
1244 int control_port = 0;
1245 int timing_port = 0;
1246 QString data = tags[
"Transport"];
1247 QStringList items = data.split(
";");
1248 bool events =
false;
1250 for (
const QString& item : std::as_const(items))
1252 if (item.startsWith(
"control_port"))
1253 control_port = item.mid(item.indexOf(
"=") + 1).trimmed().toUInt();
1254 else if (item.startsWith(
"timing_port"))
1255 timing_port = item.mid(item.indexOf(
"=") + 1).trimmed().toUInt();
1256 else if (item.startsWith(
"events"))
1260 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
1261 QString(
"Negotiated setup with client %1 on port %2")
1262 .arg(
m_socket->peerAddress().toString())
1264 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1265 QString(
"control port: %1 timing port: %2")
1266 .arg(control_port).arg(timing_port));
1278 int controlbind_port =
1281 if (controlbind_port < 0)
1283 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1284 QString(
"Failed to bind to client control port. "
1285 "Control of audio stream may fail"));
1289 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
1290 QString(
"Bound to client control port %1 on port %2")
1291 .arg(control_port).arg(controlbind_port));
1307 int timingbind_port =
1310 if (timingbind_port < 0)
1312 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1313 QString(
"Failed to bind to client timing port. "
1314 "Timing of audio stream will be incorrect"));
1318 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
1319 QString(
"Bound to client timing port %1 on port %2")
1320 .arg(timing_port).arg(timingbind_port));
1333 client->disconnect();
1351 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1352 "Failed to find a port for RAOP events server.");
1356 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
1357 QString(
"Listening for RAOP events on port %1").arg(
m_eventPort));
1372 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1373 QString(
"Audio hardware latency: %1ms")
1381 for (
const QString& item : std::as_const(items))
1387 if (item.startsWith(
"control_port"))
1389 newdata +=
"control_port=" + QString::number(controlbind_port);
1391 else if (item.startsWith(
"timing_port"))
1393 newdata +=
"timing_port=" + QString::number(timingbind_port);
1395 else if (item.startsWith(
"events"))
1397 newdata +=
"event_port=" + QString::number(
m_eventPort);
1409 newdata +=
"server_port=" + QString::number(
m_dataPort);
1420 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1421 "No Transport details found - Ignoring");
1424 else if (option ==
"RECORD")
1439 else if (option ==
"FLUSH")
1454 else if (option ==
"SET_PARAMETER")
1456 if (tags.contains(
"Content-Type"))
1458 if (tags[
"Content-Type"] ==
"text/parameters")
1463 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1464 QString(
"text/parameters: name=%1 parem=%2")
1469 float volume = (param.toFloat() + 30.0F) * 100.0F / 30.0F;
1472 LOG(VB_PLAYBACK, LOG_INFO,
1473 LOC + QString(
"Setting volume to %1 (raw %3)")
1474 .arg(volume).arg(param));
1477 else if (name ==
"progress")
1479 QStringList items = param.split(
"/");
1480 if (items.size() == 3)
1491 LOG(VB_PLAYBACK, LOG_INFO,
1492 LOC +QString(
"Progress: %1/%2")
1498 else if(tags[
"Content-Type"] ==
"image/none")
1503 else if(tags[
"Content-Type"].startsWith(
"image/"))
1509 else if (tags[
"Content-Type"] ==
"application/x-dmap-tagged")
1513 LOG(VB_PLAYBACK, LOG_INFO,
1514 QString(
"Receiving Title:%1 Artist:%2 Album:%3 Format:%4")
1521 else if (option ==
"GET_PARAMETER")
1523 if (tags.contains(
"Content-Type"))
1525 if (tags[
"Content-Type"] ==
"text/parameters")
1529 for (
const QString& line : std::as_const(lines))
1531 if (line ==
"volume")
1535 responseData += QString(
"volume: %1\r\n")
1536 .arg((volume * 30.0F / 100.0F) - 30.0F,1,
'f',6,
'0');
1542 else if (option ==
"TEARDOWN")
1549 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Command not handled: %1")
1561 *stream <<
"RTSP/1.0 401 Unauthorised\r\n";
1562 *stream <<
"Server: AirTunes/130.14\r\n";
1563 *stream <<
"WWW-Authenticate: Digest realm=\"raop\", ";
1564 *stream <<
"nonce=\"" +
m_nonce +
"\"\r\n";
1565 *stream <<
"CSeq: " << cseq <<
"\r\n";
1568 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1569 QString(
"Finished Authentication request %2, Send: %3")
1570 .arg(cseq).arg(socket->flush()));
1574 QString &option, QString &cseq, QString &responseData)
1578 *stream <<
"Server: AirTunes/130.14\r\n";
1579 *stream <<
"CSeq: " << cseq <<
"\r\n";
1580 if (!responseData.isEmpty())
1581 *stream <<
"Content-Length: " << QString::number(responseData.length()) <<
"\r\n\r\n" << responseData;
1585 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Finished %1 %2 , Send: %3")
1586 .arg(option, cseq, QString::number(socket->flush())));
1596 static QMutex s_lock;
1597 QMutexLocker locker(&s_lock);
1602 QString sName(
"/RAOPKey.rsa" );
1618 int type = EVP_PKEY_type(
id);
1619 if (
type != EVP_PKEY_RSA)
1628 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
"Loaded RSA private key");
1640 if (lines.isEmpty())
1643 for (
const QString& line : std::as_const(lines))
1645 int index = line.indexOf(
":");
1648 result.insert(line.left(index).trimmed(),
1649 line.mid(index + 1).trimmed());
1658 QTextStream stream(lines);
1660 QString line = stream.readLine();
1661 while (!line.isEmpty())
1664 line = stream.readLine();
1679 QTime time = QTime(0,0).addSecs(timeInSeconds);
1680 return time.toString(time.hour() > 0 ?
"HH:mm:ss" :
"mm:ss");
1690 return std::chrono::milliseconds((frames * 1000ULL) /
m_frameRate);
1707 QMap<QString,QString> result;
1709 while (offset < dmap.size())
1711 QString tag = dmap.mid(offset, 4);
1713 uint32_t length = qFromBigEndian(*(uint32_t *)(dmap.constData() + offset));
1714 offset +=
sizeof(uint32_t);
1715 QString
content = QString::fromUtf8(dmap.mid(offset,
1716 length).constData());
1728 m_codec = avcodec_find_decoder(AV_CODEC_ID_ALAC);
1731 LOG(VB_PLAYBACK, LOG_ERR,
LOC
1732 +
"Failed to create ALAC decoder- going silent...");
1739 auto *extradata =
new unsigned char[36];
1740 memset(extradata, 0, 36);
1743 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1744 "Creating decoder but haven't seen audio format.");
1749 extradata[12] = (fs >> 24) & 0xff;
1750 extradata[13] = (fs >> 16) & 0xff;
1751 extradata[14] = (fs >> 8) & 0xff;
1752 extradata[15] = fs & 0xff;
1764 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1765 "Failed to open ALAC decoder - going silent...");
1769 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
"Opened ALAC decoder.");
1798 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1799 "Failed to open audio device. Going silent...");
1806 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
"Opened audio device.");
1852 usleep(duration_cast<std::chrono::microseconds>(
AUDIOCARD_BUFFER).count());
1854 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"AudioCardLatency: ts=%1ms")
1855 .arg(audiots.count()));
1861 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
1862 QString(
"New connection from %1:%2 for RAOP events server.")
1863 .arg(client->peerAddress().toString()).arg(client->peerPort()));
1871 auto *client = qobject_cast<QTcpSocket *>(sender());
1874 if (client !=
nullptr)
1875 label = QString(
"%1:%2").arg(client->peerAddress().toString()).arg(client->peerPort());
1877 label = QString(
"unknown");
1879 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1880 QString(
"%1 disconnected from RAOP events server.").arg(label));
1886 auto duration = std::chrono::seconds(
1896 image,
m_dmap, duration, position);
1901 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