11#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
12#include <QStringConverter>
32#if OPENSSL_VERSION_NUMBER < 0x030000000L
33#define EVP_PKEY_get_id EVP_PKEY_id
34#define EVP_PKEY_get_size EVP_PKEY_size
37#define LOC QString("RAOP Conn: ")
46static constexpr uint8_t
SYNC { 0x54 };
68 LOG(VB_PLAYBACK, LOG_DEBUG,
69 LOC + QString(
"Sending(%1): ").arg(str.length()) + str.trimmed());
70 QTextStream *q =
this;
77 QByteArray
id,
int port)
80 m_hardwareId(
std::move(id)),
82 m_cctx(EVP_CIPHER_CTX_new()),
85#if OPENSSL_VERSION_NUMBER < 0x030000000L
89 m_cipher = EVP_CIPHER_fetch(
nullptr,
"AES-128-CBC",
nullptr);
100 client->deleteLater();
131 nc->UnRegister(
this,
m_id);
134 EVP_CIPHER_CTX_free(
m_cctx);
136#if OPENSSL_VERSION_NUMBER >= 0x030000000L
207#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
214 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
"Failed to connect client socket signal.");
223 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
"Failed to connect data socket signal.");
231 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
"Failed to bind to a port for data.");
235 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
236 QString(
"Bound to port %1 for incoming data").arg(
m_dataPort));
272 uint64_t firstframe = 0;
276 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
277 QString(
"Packet doesn't start with valid Rtp Header (0x%1)")
278 .arg((uint8_t)buf[0], 0, 16));
308 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
309 QString(
"Packet type (0x%1) not handled")
317 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
318 QString(
"Received packet %1 too late, ignoring")
341 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
342 QString(
"Received required resend %1 (with ts:%2 last:%3)")
348 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
349 QString(
"Received unexpected resent packet %1")
356 auto *decoded =
new QList<AudioData>();
361 LOG(VB_PLAYBACK, LOG_ERR,
LOC + QString(
"Error decoding audio"));
373 bool first = (uint8_t)buf[0] == 0x90;
374 const char *req = buf.constData();
375 uint64_t current_ts = qFromBigEndian(*(uint32_t *)(req + 4));
376 uint64_t next_ts = qFromBigEndian(*(uint32_t *)(req + 16));
388 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Receiving %1SYNC packet")
389 .arg(first ?
"first " :
""));
393 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"SYNC: cur:%1 next:%2 time:%3")
399 std::chrono::milliseconds currentLatency = 0ms;
406 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
407 QString(
"RAOP timestamps: about to play:%1 desired:%2 latency:%3")
409 .arg(currentLatency.count()));
412 delay += currentLatency;
414 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
415 QString(
"Queue=%1 buffer=%2ms ideal=%3ms diffts:%4ms")
429 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
430 QString(
"Too much delay (%1ms), adjusting")
440 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Drop %1 packets").arg(res));
457 int16_t missed = (got < expected) ?
458 (int16_t)(((int32_t)got + UINT16_MAX + 1) - expected) :
461 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
462 QString(
"Missed %1 packet(s): expected %2 got %3 ts:%4")
463 .arg(missed).arg(expected).arg(got).arg(timestamp.count()));
467 *(
uint16_t *)(&req[4]) = qToBigEndian(expected);
468 *(
uint16_t *)(&req[6]) = qToBigEndian(missed);
472 == (qint64)req.size())
474 for (
uint16_t count = 0; count < missed; count++)
476 LOG(VB_PLAYBACK, LOG_INFO,
LOC + QString(
"Sent resend for %1")
477 .arg(expected + count));
478 m_resends.insert(expected + count, timestamp);
483 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
"Failed to send resend request.");
497 QMutableMapIterator<uint16_t,std::chrono::milliseconds> it(
m_resends);
503 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
504 QString(
"Never received resend packet %1").arg(it.key()));
520 uint32_t ntpTicks {0};
521 auto usecs = nowAsDuration<std::chrono::microseconds>();
524 std::array<uint8_t,32> req {
530 *(uint32_t *)(&req[24]) = qToBigEndian(ntpSec);
531 *(uint32_t *)(&req[28]) = qToBigEndian(ntpTicks);
536 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
"Failed to send resend time request.");
539 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
540 QString(
"Requesting master time (Local %1.%2)")
541 .arg(ntpSec,8,16,QChar(
'0')).arg(ntpTicks,8,16,QChar(
'0')));
552 const char *req = buf.constData();
554 uint32_t ntpSec = qFromBigEndian(*(uint32_t *)(req + 8));
555 uint32_t ntpTicks = qFromBigEndian(*(uint32_t *)(req + 12));
557 auto time2 = nowAsDuration<std::chrono::milliseconds>();
558 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Read back time (Local %1.%2)")
559 .arg(ntpSec,8,16,QChar(
'0')).arg(ntpTicks,8,16,QChar(
'0')));
563 LOG(VB_AUDIO, LOG_DEBUG,
LOC + QString(
"Network Latency: %1ms")
568 uint32_t sec = qFromBigEndian(*(uint32_t *)(req + 24));
569 uint32_t ticks = qFromBigEndian(*(uint32_t *)(req + 28));
570 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Source NTP clock time %1.%2")
571 .arg(sec,8,16,QChar(
'0')).arg(ticks,8,16,QChar(
'0')));
574 std::chrono::milliseconds master =
NTPToLocal(sec, ticks);
590 return std::chrono::milliseconds((((int64_t)sec -
CLOCK_EPOCH) * 1000LL) +
591 (((int64_t)ticks * 1000LL) >> 32));
594 uint32_t &ntpSec, uint32_t &ntpTicks)
596 ntpSec = duration_cast<std::chrono::seconds>(usec).count() +
CLOCK_EPOCH;
597 ntpTicks = ((usec % 1s).count() << 32) / 1000000;
601 auto micros = durationFromTimeval<std::chrono::microseconds>(
t);
619 if ((uint8_t)buf[0] != 0x80 && (uint8_t)buf[0] != 0x90)
638 const char *ptr = buf.constData();
643 seq = qFromBigEndian(*(
uint16_t *)(ptr + 2));
644 timestamp = qFromBigEndian(*(uint32_t *)(ptr + 4));
651 const QByteArray *buf,
652 QList<AudioData> *
dest)
654 const char *data_in = buf->constData();
655 int len = buf->size();
666 int aeslen = len & ~0xf;
669 std::array<uint8_t,MAX_PACKET_SIZE> decrypted_data {};
670 EVP_CIPHER_CTX_reset(
m_cctx);
671 EVP_CIPHER_CTX_set_padding(
m_cctx, 0);
673#
if OPENSSL_VERSION_NUMBER < 0x030000000L
676 reinterpret_cast<const uint8_t *
>(
m_aesIV.data()))
679 reinterpret_cast<const uint8_t *
>(
m_aesIV.data()),
684 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
685 QString(
"EVP_DecryptInit_ex failed. (%1)")
686 .arg(ERR_error_string(ERR_get_error(),
nullptr)));
688 else if (EVP_DecryptUpdate(
m_cctx, decrypted_data.data(), &outlen1,
689 reinterpret_cast<const uint8_t *
>(data_in),
692 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
693 QString(
"EVP_DecryptUpdate failed. (%1)")
694 .arg(ERR_error_string(ERR_get_error(),
nullptr)));
696 else if (EVP_DecryptFinal_ex(
m_cctx, decrypted_data.data() + outlen1, &outlen2) != 1)
698 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
699 QString(
"EVP_DecryptFinal_ex failed. (%1)")
700 .arg(ERR_error_string(ERR_get_error(),
nullptr)));
702 std::copy(data_in + aeslen, data_in + len,
703 decrypted_data.data() + aeslen);
706 AVPacket *tmp_pkt = av_packet_alloc();
707 if (tmp_pkt ==
nullptr)
709 tmp_pkt->data = decrypted_data.data();
712 uint32_t frames_added = 0;
714 while (tmp_pkt->size > 0)
727 int num_samples = data_size /
728 (ctx->ch_layout.nb_channels * av_get_bytes_per_sample(ctx->sample_fmt));
730 frames_added += num_samples;
734 tmp_pkt->data += ret;
735 tmp_pkt->size -= ret;
737 av_packet_free(&tmp_pkt);
752 auto dtime = nowAsDuration<std::chrono::milliseconds>() -
m_timeLastSync;
773 std::chrono::milliseconds timestamp = 0ms;
775 QMapIterator<std::chrono::milliseconds,AudioPacket> packet_it(
m_audioQueue);
776 while (packet_it.hasNext() && i <= max_packets)
780 timestamp = packet_it.key();
791 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
792 QString(
"Audio discontinuity seen. Played %1 (%3) expected %2")
798 for (
const auto & data : std::as_const(*frames.
data))
808 offset = std::min(offset, data.length);
811 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
812 QString(
"ProcessAudio: Dropping %1 frames to catch up "
818 data.length - offset,
819 std::chrono::milliseconds(timestamp), framecnt);
843 QMutableMapIterator<std::chrono::milliseconds,AudioPacket> packet_it(
m_audioQueue);
844 while (packet_it.hasNext())
847 if (packet_it.key() < timestamp)
852 for (
const auto & data : std::as_const(*frames.
data))
876 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"Closing connection after inactivity.");
899 auto *socket = qobject_cast<QTcpSocket *>(sender());
903 QByteArray data = socket->readAll();
906 QByteArray printable = data.left(32);
907 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"readClient(%1): %2%3")
909 .arg(printable.toHex().toUpper().data(),
910 data.size() > 32 ?
"..." :
""));
919 QTextStream stream(data);
920 QString line = stream.readLine();
921 while (!line.isEmpty())
923 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Header(%1) = %2")
924 .arg(
m_socket->peerAddress().toString(), line));
926 if (line.contains(
"Content-Length:"))
930 line = stream.readLine();
938 int pos = stream.pos();
958 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Content(%1) = %2")
967 if (header.isEmpty())
972 if (!tags.contains(
"CSeq"))
974 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
"ProcessRequest: Didn't find CSeq");
978 QString option = header[0].left(header[0].indexOf(
" "));
983 uint64_t RTPtimestamp = 0;
984 if (tags.contains(
"RTP-Info"))
987 QString data = tags[
"RTP-Info"];
988 QStringList items = data.split(
";");
989 for (
const QString& item : std::as_const(items))
991 if (item.startsWith(
"seq"))
993 RTPseq = item.mid(item.indexOf(
"=") + 1).trimmed().toUShort();
995 else if (item.startsWith(
"rtptime"))
997 RTPtimestamp = item.mid(item.indexOf(
"=") + 1).trimmed().toUInt();
1000 LOG(VB_PLAYBACK, LOG_INFO,
LOC + QString(
"RTP-Info: seq=%1 rtptime=%2")
1001 .arg(RTPseq).arg(RTPtimestamp));
1010 if (!tags.contains(
"Authorization"))
1023 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"RAOP client authenticated");
1027 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"RAOP authentication failed");
1034 if (tags.contains(
"Apple-Challenge"))
1036 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Received Apple-Challenge"));
1042 std::vector<uint8_t>to;
1045 QByteArray challenge =
1046 QByteArray::fromBase64(tags[
"Apple-Challenge"].toLatin1());
1047 int challenge_size = challenge.size();
1048 if (challenge_size != 16)
1050 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1051 QString(
"Base64 decoded challenge size %1, expected 16")
1052 .arg(challenge_size));
1053 challenge_size = std::min(challenge_size, 16);
1057 std::array<uint8_t,38> from {};
1058 std::copy(challenge.cbegin(), challenge.cbegin() + challenge_size,
1060 i += challenge_size;
1061 if (
m_socket->localAddress().protocol() ==
1062 QAbstractSocket::IPv4Protocol)
1064 uint32_t ip =
m_socket->localAddress().toIPv4Address();
1065 ip = qToBigEndian(ip);
1066 memcpy(&from[i], &ip, 4);
1069 else if (
m_socket->localAddress().protocol() ==
1070 QAbstractSocket::IPv6Protocol)
1072 Q_IPV6ADDR ip =
m_socket->localAddress().toIPv6Address();
1074 "\x00\x00\x00\x00" "\x00\x00\x00\x00" "\x00\x00\xff\xff",
1077 memcpy(&from[i], &ip[12], 4);
1082 memcpy(&from[i], &ip, 16);
1093 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1094 QString(
"Hardware MAC address size %1, expected %2")
1101 memset(&from[i], 0, pad);
1105 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1106 QString(
"Full base64 response: '%1' size %2")
1107 .arg(QByteArray((
char *)from.data(), i).toBase64().constData())
1111 if (
nullptr == pctx)
1113 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
1114 QString(
"Cannot create ENV_PKEY_CTX from key. (%1)")
1115 .arg(ERR_error_string(ERR_get_error(),
nullptr)));
1117 else if (EVP_PKEY_sign_init(pctx) <= 0)
1119 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
1120 QString(
"EVP_PKEY_sign_init failed. (%1)")
1121 .arg(ERR_error_string(ERR_get_error(),
nullptr)));
1123 else if (EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PADDING) <= 0)
1125 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
1126 QString(
"Cannot set RSA_PKCS1_PADDING on context. (%1)")
1127 .arg(ERR_error_string(ERR_get_error(),
nullptr)));
1129 else if (EVP_PKEY_sign(pctx, to.data(), &tosize,
1130 from.data(), i) <= 0)
1132 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
1133 QString(
"EVP_PKEY_sign failed. (%1)")
1134 .arg(ERR_error_string(ERR_get_error(),
nullptr)));
1137 QByteArray base64 = QByteArray((
const char *)to.data(), tosize).toBase64();
1139 for (
int pos = base64.size() - 1; pos > 0; pos--)
1141 if (base64[pos] ==
'=')
1146 LOG(VB_PLAYBACK, LOG_DEBUG, QString(
"tSize=%1 tLen=%2 tResponse=%3")
1147 .arg(tosize).arg(base64.size()).arg(base64.constData()));
1151 QString responseData;
1152 if (option ==
"OPTIONS")
1154 *
m_textStream <<
"Public: ANNOUNCE, SETUP, RECORD, PAUSE, FLUSH, "
1155 "TEARDOWN, OPTIONS, GET_PARAMETER, SET_PARAMETER, POST, GET\r\n";
1157 else if (option ==
"ANNOUNCE")
1160 for (
const QString& line : std::as_const(lines))
1162 if (line.startsWith(
"a=rsaaeskey:"))
1164 QString key = line.mid(12).trimmed();
1165 QByteArray decodedkey = QByteArray::fromBase64(key.toLatin1());
1166 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1167 QString(
"RSAAESKey: %1 (base64 decoded size %2)")
1168 .arg(key).arg(decodedkey.size()));
1176 size_t size_out {size};
1177 if (
nullptr == pctx)
1179 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
1180 QString(
"Cannot create ENV_PKEY_CTX from key. (%1)")
1181 .arg(ERR_error_string(ERR_get_error(),
nullptr)));
1183 else if (EVP_PKEY_decrypt_init(pctx) <= 0)
1185 LOG(VB_PLAYBACK, LOG_WARNING,
LOC + QString(
"EVP_PKEY_decrypt_init failed. (%1)")
1186 .arg(ERR_error_string(ERR_get_error(),
nullptr)));
1188 else if (EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_OAEP_PADDING) <= 0)
1190 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
1191 QString(
"Cannot set RSA_PKCS1_OAEP_PADDING on context. (%1)")
1192 .arg(ERR_error_string(ERR_get_error(),
nullptr)));
1194 else if (EVP_PKEY_decrypt(pctx,
m_sessionKey.data(), &size_out,
1195 (
const unsigned char *)decodedkey.constData(), decodedkey.size()) > 0)
1198 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1199 "Successfully decrypted AES key from RSA.");
1203 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
1204 QString(
"Failed to decrypt AES key from RSA. (%1)")
1205 .arg(ERR_error_string(ERR_get_error(),
nullptr)));
1209 else if (line.startsWith(
"a=aesiv:"))
1211 QString aesiv = line.mid(8).trimmed();
1212 m_aesIV = QByteArray::fromBase64(aesiv.toLatin1());
1213 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1214 QString(
"AESIV: %1 (base64 decoded size %2)")
1215 .arg(aesiv).arg(
m_aesIV.size()));
1217 else if (line.startsWith(
"a=fmtp:"))
1220 QString format = line.mid(7).trimmed();
1221 QList<QString>
formats = format.split(
' ');
1222 for (
const QString& fmt : std::as_const(
formats))
1226 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1227 QString(
"Audio parameter: %1").arg(fmt));
1235 else if (option ==
"SETUP")
1237 if (tags.contains(
"Transport"))
1240 auto *dev = qobject_cast<MythRAOPDevice*>(parent());
1242 dev->DeleteAllClients(
this);
1246 int control_port = 0;
1247 int timing_port = 0;
1248 QString data = tags[
"Transport"];
1249 QStringList items = data.split(
";");
1250 bool events =
false;
1252 for (
const QString& item : std::as_const(items))
1254 if (item.startsWith(
"control_port"))
1255 control_port = item.mid(item.indexOf(
"=") + 1).trimmed().toUInt();
1256 else if (item.startsWith(
"timing_port"))
1257 timing_port = item.mid(item.indexOf(
"=") + 1).trimmed().toUInt();
1258 else if (item.startsWith(
"events"))
1262 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
1263 QString(
"Negotiated setup with client %1 on port %2")
1264 .arg(
m_socket->peerAddress().toString())
1266 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1267 QString(
"control port: %1 timing port: %2")
1268 .arg(control_port).arg(timing_port));
1280 int controlbind_port =
1283 if (controlbind_port < 0)
1285 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1286 QString(
"Failed to bind to client control port. "
1287 "Control of audio stream may fail"));
1291 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
1292 QString(
"Bound to client control port %1 on port %2")
1293 .arg(control_port).arg(controlbind_port));
1309 int timingbind_port =
1312 if (timingbind_port < 0)
1314 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1315 QString(
"Failed to bind to client timing port. "
1316 "Timing of audio stream will be incorrect"));
1320 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
1321 QString(
"Bound to client timing port %1 on port %2")
1322 .arg(timing_port).arg(timingbind_port));
1335 client->disconnect();
1353 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1354 "Failed to find a port for RAOP events server.");
1358 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
1359 QString(
"Listening for RAOP events on port %1").arg(
m_eventPort));
1374 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1375 QString(
"Audio hardware latency: %1ms")
1383 for (
const QString& item : std::as_const(items))
1389 if (item.startsWith(
"control_port"))
1391 newdata +=
"control_port=" + QString::number(controlbind_port);
1393 else if (item.startsWith(
"timing_port"))
1395 newdata +=
"timing_port=" + QString::number(timingbind_port);
1397 else if (item.startsWith(
"events"))
1399 newdata +=
"event_port=" + QString::number(
m_eventPort);
1411 newdata +=
"server_port=" + QString::number(
m_dataPort);
1422 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1423 "No Transport details found - Ignoring");
1426 else if (option ==
"RECORD")
1441 else if (option ==
"FLUSH")
1456 else if (option ==
"SET_PARAMETER")
1458 if (tags.contains(
"Content-Type"))
1460 if (tags[
"Content-Type"] ==
"text/parameters")
1465 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1466 QString(
"text/parameters: name=%1 parem=%2")
1471 float volume = (param.toFloat() + 30.0F) * 100.0F / 30.0F;
1474 LOG(VB_PLAYBACK, LOG_INFO,
1475 LOC + QString(
"Setting volume to %1 (raw %3)")
1476 .arg(volume).arg(param));
1479 else if (name ==
"progress")
1481 QStringList items = param.split(
"/");
1482 if (items.size() == 3)
1493 LOG(VB_PLAYBACK, LOG_INFO,
1494 LOC +QString(
"Progress: %1/%2")
1500 else if(tags[
"Content-Type"] ==
"image/none")
1505 else if(tags[
"Content-Type"].startsWith(
"image/"))
1511 else if (tags[
"Content-Type"] ==
"application/x-dmap-tagged")
1515 LOG(VB_PLAYBACK, LOG_INFO,
1516 QString(
"Receiving Title:%1 Artist:%2 Album:%3 Format:%4")
1523 else if (option ==
"GET_PARAMETER")
1525 if (tags.contains(
"Content-Type"))
1527 if (tags[
"Content-Type"] ==
"text/parameters")
1531 for (
const QString& line : std::as_const(lines))
1533 if (line ==
"volume")
1537 responseData += QString(
"volume: %1\r\n")
1538 .arg((volume * 30.0F / 100.0F) - 30.0F,1,
'f',6,
'0');
1544 else if (option ==
"TEARDOWN")
1551 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Command not handled: %1")
1563 *stream <<
"RTSP/1.0 401 Unauthorised\r\n";
1564 *stream <<
"Server: AirTunes/130.14\r\n";
1565 *stream <<
"WWW-Authenticate: Digest realm=\"raop\", ";
1566 *stream <<
"nonce=\"" +
m_nonce +
"\"\r\n";
1567 *stream <<
"CSeq: " << cseq <<
"\r\n";
1570 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1571 QString(
"Finished Authentication request %2, Send: %3")
1572 .arg(cseq).arg(socket->flush()));
1576 QString &option, QString &cseq, QString &responseData)
1580 *stream <<
"Server: AirTunes/130.14\r\n";
1581 *stream <<
"CSeq: " << cseq <<
"\r\n";
1582 if (!responseData.isEmpty())
1583 *stream <<
"Content-Length: " << QString::number(responseData.length()) <<
"\r\n\r\n" << responseData;
1587 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Finished %1 %2 , Send: %3")
1588 .arg(option, cseq, QString::number(socket->flush())));
1598 static QMutex s_lock;
1599 QMutexLocker locker(&s_lock);
1604 QString sName(
"/RAOPKey.rsa" );
1620 int type = EVP_PKEY_type(
id);
1621 if (
type != EVP_PKEY_RSA)
1630 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
"Loaded RSA private key");
1642 if (lines.isEmpty())
1645 for (
const QString& line : std::as_const(lines))
1647 int index = line.indexOf(
":");
1650 result.insert(line.left(index).trimmed(),
1651 line.mid(index + 1).trimmed());
1660 QTextStream stream(lines);
1662 QString line = stream.readLine();
1663 while (!line.isEmpty())
1666 line = stream.readLine();
1681 QTime time = QTime(0,0).addSecs(timeInSeconds);
1682 return time.toString(time.hour() > 0 ?
"HH:mm:ss" :
"mm:ss");
1692 return std::chrono::milliseconds((frames * 1000ULL) /
m_frameRate);
1709 QMap<QString,QString> result;
1711 while (offset < dmap.size())
1713 QString tag = dmap.mid(offset, 4);
1715 uint32_t length = qFromBigEndian(*(uint32_t *)(dmap.constData() + offset));
1716 offset +=
sizeof(uint32_t);
1717 QString
content = QString::fromUtf8(dmap.mid(offset,
1718 length).constData());
1730 m_codec = avcodec_find_decoder(AV_CODEC_ID_ALAC);
1733 LOG(VB_PLAYBACK, LOG_ERR,
LOC
1734 +
"Failed to create ALAC decoder- going silent...");
1741 auto *extradata =
new unsigned char[36];
1742 memset(extradata, 0, 36);
1745 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1746 "Creating decoder but haven't seen audio format.");
1751 extradata[12] = (fs >> 24) & 0xff;
1752 extradata[13] = (fs >> 16) & 0xff;
1753 extradata[14] = (fs >> 8) & 0xff;
1754 extradata[15] = fs & 0xff;
1766 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1767 "Failed to open ALAC decoder - going silent...");
1771 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
"Opened ALAC decoder.");
1800 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1801 "Failed to open audio device. Going silent...");
1808 if (!
error.isEmpty())
1810 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1811 QString(
"Audio not initialised. Message was '%1'")
1819 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
"Opened audio device.");
1865 usleep(duration_cast<std::chrono::microseconds>(
AUDIOCARD_BUFFER).count());
1867 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"AudioCardLatency: ts=%1ms")
1868 .arg(audiots.count()));
1874 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
1875 QString(
"New connection from %1:%2 for RAOP events server.")
1876 .arg(client->peerAddress().toString()).arg(client->peerPort()));
1884 auto *client = qobject_cast<QTcpSocket *>(sender());
1887 if (client !=
nullptr)
1888 label = QString(
"%1:%2").arg(client->peerAddress().toString()).arg(client->peerPort());
1890 label = QString(
"unknown");
1892 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1893 QString(
"%1 disconnected from RAOP events server.").arg(label));
1899 auto duration = std::chrono::seconds(
1909 image,
m_dmap, duration, position);
1914 duration, position);
static int DecodeAudio(AVCodecContext *ctx, uint8_t *buffer, int &data_size, const AVPacket *pkt)
DecodeAudio Decode an audio packet, and compact it if data is planar Return negative error code if an...
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
virtual void Reset(void)=0
virtual void Pause(bool paused)=0
QString GetError(void) const
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
const std::array< const std::string, 8 > formats