MythTV master
mythraopconnection.cpp
Go to the documentation of this file.
1#include <unistd.h> // for usleep()
2
3#include <algorithm>
4#include <limits> // workaround QTBUG-90395
5#include <utility>
6
7#include <QTcpSocket>
8#include <QTextStream>
9#include <QTimer>
10#include <QtEndian>
11#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
12#include <QStringConverter>
13#endif
14
19
21
22#include "mythraopdevice.h"
23#include "mythraopconnection.h"
24#include "mythairplayserver.h"
26
28
29// OpenSSL 3.0.0 release
30// 8 bits major, 8 bits minor 24 bits patch, 4 bits release flag.
31#if OPENSSL_VERSION_NUMBER < 0x030000000L
32#define EVP_PKEY_get_id EVP_PKEY_id
33#define EVP_PKEY_get_size EVP_PKEY_size
34#endif
35
36#define LOC QString("RAOP Conn: ")
37static constexpr size_t MAX_PACKET_SIZE { 2048 };
38
39EVP_PKEY *MythRAOPConnection::g_devPrivKey = nullptr;
41
42// RAOP RTP packet type
43static constexpr uint8_t TIMING_REQUEST { 0x52 };
44static constexpr uint8_t TIMING_RESPONSE { 0x53 };
45static constexpr uint8_t SYNC { 0x54 };
46static constexpr uint8_t FIRSTSYNC { 0x54 | 0x80 };
47static constexpr uint8_t RANGE_RESEND { 0x55 };
48static constexpr uint8_t AUDIO_RESEND { 0x56 };
49static constexpr uint8_t AUDIO_DATA { 0x60 };
50static constexpr uint8_t FIRSTAUDIO_DATA { 0x60 | 0x80 };
51
52// Size (in ms) of audio buffered in audio card
53static constexpr std::chrono::milliseconds AUDIOCARD_BUFFER { 500ms };
54// How frequently we may call ProcessAudio (via QTimer)
55// ideally 20ms, but according to documentation
56// anything lower than 50ms on windows, isn't reliable
57static constexpr std::chrono::milliseconds AUDIO_BUFFER { 100ms };
58
59class RaopNetStream : public QTextStream
60{
61public:
62 explicit RaopNetStream(QIODevice *device) : QTextStream(device)
63 {
64 };
65 RaopNetStream &operator<<(const QString &str)
66 {
67 LOG(VB_PLAYBACK, LOG_DEBUG,
68 LOC + QString("Sending(%1): ").arg(str.length()) + str.trimmed());
69 QTextStream *q = this;
70 *q << str;
71 return *this;
72 };
73};
74
75MythRAOPConnection::MythRAOPConnection(QObject *parent, QTcpSocket *socket,
76 QByteArray id, int port)
77 : QObject(parent),
78 m_socket(socket),
79 m_hardwareId(std::move(id)),
80 m_dataPort(port),
81 m_cctx(EVP_CIPHER_CTX_new()),
82 m_id(GetNotificationCenter()->Register(this))
83{
84#if OPENSSL_VERSION_NUMBER < 0x030000000L
85 m_cipher = EVP_aes_128_cbc();
86#else
87 //NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
88 m_cipher = EVP_CIPHER_fetch(nullptr, "AES-128-CBC", nullptr);
89#endif
90}
91
93{
94 CleanUp();
95
96 for (QTcpSocket *client : std::as_const(m_eventClients))
97 {
98 client->close();
99 client->deleteLater();
100 }
101 m_eventClients.clear();
102
103 if (m_eventServer)
104 {
105 m_eventServer->disconnect();
107 m_eventServer->deleteLater();
108 m_eventServer = nullptr;
109 }
110
111 // delete main socket
112 if (m_socket)
113 {
114 m_socket->disconnect();
115 m_socket->close();
116 m_socket->deleteLater();
117 m_socket = nullptr;
118 }
119
120 if (m_textStream)
121 {
122 delete m_textStream;
123 m_textStream = nullptr;
124 }
125
126 if (m_id > 0)
127 {
128 auto *nc = GetNotificationCenter();
129 if (nc)
130 nc->UnRegister(this, m_id);
131 }
132
133 EVP_CIPHER_CTX_free(m_cctx);
134 m_cctx = nullptr;
135#if OPENSSL_VERSION_NUMBER >= 0x030000000L
136 EVP_CIPHER_free(m_cipher);
137#endif
138 m_cipher = nullptr;
139}
140
142{
143 // delete audio timer
145
146 // stop and delete watchdog timer
147 if (m_watchdogTimer)
148 {
149 m_watchdogTimer->stop();
150 delete m_watchdogTimer;
151 m_watchdogTimer = nullptr;
152 }
153
155 {
156 m_dequeueAudioTimer->stop();
157 delete m_dequeueAudioTimer;
158 m_dequeueAudioTimer = nullptr;
159 }
160
162 {
163 m_clientTimingSocket->disconnect();
166 m_clientTimingSocket = nullptr;
167 }
168
169 // delete data socket
170 if (m_dataSocket)
171 {
172 m_dataSocket->disconnect();
174 m_dataSocket->deleteLater();
175 m_dataSocket = nullptr;
176 }
177
178 // client control socket
180 {
181 m_clientControlSocket->disconnect();
183 m_clientControlSocket->deleteLater();
184 m_clientControlSocket = nullptr;
185 }
186
187 // close audio decoder
189
190 // free decoded audio buffer
191 ResetAudio();
192
193 // close audio device
195 // Tell listeners we're done
197 {
199 }
200}
201
203{
204 // connect up the request socket
206#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
207 m_textStream->setCodec("UTF-8");
208#else
209 m_textStream->setEncoding(QStringConverter::Utf8);
210#endif
211 if (!connect(m_socket, &QIODevice::readyRead, this, &MythRAOPConnection::readClient))
212 {
213 LOG(VB_PLAYBACK, LOG_ERR, LOC + "Failed to connect client socket signal.");
214 return false;
215 }
216
217 // create the data socket
218 m_dataSocket = new ServerPool();
221 {
222 LOG(VB_PLAYBACK, LOG_ERR, LOC + "Failed to connect data socket signal.");
223 return false;
224 }
225
226 // try a few ports in case the first is in use
228 if (m_dataPort < 0)
229 {
230 LOG(VB_PLAYBACK, LOG_ERR, LOC + "Failed to bind to a port for data.");
231 return false;
232 }
233
234 LOG(VB_PLAYBACK, LOG_INFO, LOC +
235 QString("Bound to port %1 for incoming data").arg(m_dataPort));
236
237 // load the private key
238 if (!LoadKey())
239 return false;
240
241 // use internal volume control
242 m_allowVolumeControl = gCoreContext->GetBoolSetting("MythControlsVolume", true);
243
244 // start the watchdog timer to auto delete the client after a period of inactivity
245 m_watchdogTimer = new QTimer();
247 m_watchdogTimer->start(10s);
248
249 m_dequeueAudioTimer = new QTimer();
251
252 return true;
253}
254
259void MythRAOPConnection::udpDataReady(QByteArray buf, const QHostAddress& /*peer*/,
260 quint16 /*port*/)
261{
262 // restart the idle timer
263 if (m_watchdogTimer)
264 m_watchdogTimer->start(10s);
265
266 if (!m_audio || !m_codec || !m_codecContext)
267 return;
268
269 uint8_t type = 0;
270 uint16_t seq = 0;
271 uint64_t firstframe = 0;
272
273 if (!GetPacketType(buf, type, seq, firstframe))
274 {
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));
278 return;
279 }
280
281 switch (type)
282 {
283 case SYNC:
284 case FIRSTSYNC:
285 ProcessSync(buf);
286 ProcessAudio();
287 return;
288
289 case FIRSTAUDIO_DATA:
290 m_nextSequence = seq;
291 m_nextTimestamp = framesToMs(firstframe);
292 // With iTunes we know what the first sequence is going to be.
293 // iOS device do not tell us before streaming start what the first
294 // packet is going to be.
295 m_streamingStarted = true;
296 break;
297
298 case AUDIO_DATA:
299 case AUDIO_RESEND:
300 break;
301
302 case TIMING_RESPONSE:
304 return;
305
306 default:
307 LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
308 QString("Packet type (0x%1) not handled")
309 .arg(type, 0, 16));
310 return;
311 }
312
313 auto timestamp = framesToMs(firstframe);
314 if (timestamp < m_currentTimestamp)
315 {
316 LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
317 QString("Received packet %1 too late, ignoring")
318 .arg(seq));
319 return;
320 }
321 // regular data packet
322 if (type == AUDIO_DATA || type == FIRSTAUDIO_DATA)
323 {
325 SendResendRequest(timestamp, m_nextSequence, seq);
326
327 m_nextSequence = seq + 1;
328 m_nextTimestamp = timestamp;
329 m_streamingStarted = true;
330 }
331
333 return;
334
335 // resent packet
336 if (type == AUDIO_RESEND)
337 {
338 if (m_resends.contains(seq))
339 {
340 LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
341 QString("Received required resend %1 (with ts:%2 last:%3)")
342 .arg(seq).arg(firstframe).arg(m_nextSequence));
343 m_resends.remove(seq);
344 }
345 else
346 {
347 LOG(VB_PLAYBACK, LOG_WARNING, LOC +
348 QString("Received unexpected resent packet %1")
349 .arg(seq));
350 }
351 }
352
353 // Check that the audio packet is valid, do so by decoding it. If an error
354 // occurs, ask to resend it
355 auto *decoded = new QList<AudioData>();
356 int numframes = decodeAudioPacket(type, &buf, decoded);
357 if (numframes < 0)
358 {
359 // an error occurred, ask for the audio packet once again.
360 LOG(VB_PLAYBACK, LOG_ERR, LOC + QString("Error decoding audio"));
361 SendResendRequest(timestamp, seq, seq+1);
362 delete decoded;
363 return;
364 }
365 AudioPacket frames { seq, decoded };
366 m_audioQueue.insert(timestamp, frames);
367 ProcessAudio();
368}
369
370void MythRAOPConnection::ProcessSync(const QByteArray &buf)
371{
372 bool first = (uint8_t)buf[0] == 0x90; // First sync is 0x90,0x54
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));
376
377 m_currentTimestamp = framesToMs(current_ts);
378 m_nextTimestamp = framesToMs(next_ts);
380
381 if (current_ts > m_progressStart)
382 {
383 m_progressCurrent = next_ts;
384 SendNotification(true);
385 }
386
387 LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("Receiving %1SYNC packet")
388 .arg(first ? "first " : ""));
389
390 m_timeLastSync = nowAsDuration<std::chrono::milliseconds>();
391
392 LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("SYNC: cur:%1 next:%2 time:%3")
393 .arg(m_currentTimestamp.count()).arg(m_nextTimestamp.count())
394 .arg(m_timeLastSync.count()));
395
396 std::chrono::milliseconds delay = framesToMs((uint64_t)m_audioQueue.size() * m_framesPerPacket);
397 std::chrono::milliseconds audiots = m_audio->GetAudiotime();
398 std::chrono::milliseconds currentLatency = 0ms;
399
400 if (m_audioStarted)
401 {
402 currentLatency = audiots - m_currentTimestamp;
403 }
404
405 LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
406 QString("RAOP timestamps: about to play:%1 desired:%2 latency:%3")
407 .arg(audiots.count()).arg(m_currentTimestamp.count())
408 .arg(currentLatency.count()));
409
410 delay += m_audio->GetAudioBufferedTime();
411 delay += currentLatency;
412
413 LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
414 QString("Queue=%1 buffer=%2ms ideal=%3ms diffts:%4ms")
415 .arg(m_audioQueue.size())
416 .arg(delay.count())
417 .arg(m_bufferLength.count())
418 .arg((m_bufferLength-delay).count()));
419
420 if (m_adjustedLatency <= 0ms && m_audioStarted &&
421 (-currentLatency > AUDIOCARD_BUFFER))
422 {
423 // Too much delay in playback.
424 // The threshold is a value chosen to be loose enough so it doesn't
425 // trigger too often, but should be low enough to detect any accidental
426 // interruptions.
427 // Will drop some frames in next ProcessAudio
428 LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
429 QString("Too much delay (%1ms), adjusting")
430 .arg((m_bufferLength - delay).count()));
431
433
434 // Expire old audio
437 if (res > 0)
438 {
439 LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("Drop %1 packets").arg(res));
440 }
441
442 m_audioStarted = false;
443 }
444}
445
450void MythRAOPConnection::SendResendRequest(std::chrono::milliseconds timestamp,
451 uint16_t expected, uint16_t got)
452{
454 return;
455
456 int16_t missed = (got < expected) ?
457 (int16_t)(((int32_t)got + UINT16_MAX + 1) - expected) :
458 got - expected;
459
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()));
463
464 std::array<uint8_t,8> req { 0x80, RANGE_RESEND | 0x80};
465 *(uint16_t *)(&req[2]) = qToBigEndian(m_seqNum++);
466 *(uint16_t *)(&req[4]) = qToBigEndian(expected); // missed seqnum
467 *(uint16_t *)(&req[6]) = qToBigEndian(missed); // count
468
469 if (m_clientControlSocket->writeDatagram((char *)req.data(), req.size(),
471 == (qint64)req.size())
472 {
473 for (uint16_t count = 0; count < missed; count++)
474 {
475 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Sent resend for %1")
476 .arg(expected + count));
477 m_resends.insert(expected + count, timestamp);
478 }
479 }
480 else
481 {
482 LOG(VB_PLAYBACK, LOG_ERR, LOC + "Failed to send resend request.");
483 }
484}
485
491void MythRAOPConnection::ExpireResendRequests(std::chrono::milliseconds timestamp)
492{
493 if (m_resends.isEmpty())
494 return;
495
496 QMutableMapIterator<uint16_t,std::chrono::milliseconds> it(m_resends);
497 while (it.hasNext())
498 {
499 it.next();
500 if (it.value() < timestamp && m_streamingStarted)
501 {
502 LOG(VB_PLAYBACK, LOG_WARNING, LOC +
503 QString("Never received resend packet %1").arg(it.key()));
504 m_resends.remove(it.key());
505 }
506 }
507}
508
514{
515 if (!m_clientControlSocket) // should never happen
516 return;
517
518 uint32_t ntpSec {0};
519 uint32_t ntpTicks {0};
520 auto usecs = nowAsDuration<std::chrono::microseconds>();
521 microsecondsToNTP(usecs, ntpSec, ntpTicks);
522
523 std::array<uint8_t,32> req {
524 0x80, TIMING_REQUEST | 0x80,
525 // this is always 0x00 0x07 according to http://blog.technologeek.org/airtunes-v2
526 // no other value works
527 0x00, 0x07
528 };
529 *(uint32_t *)(&req[24]) = qToBigEndian(ntpSec);
530 *(uint32_t *)(&req[28]) = qToBigEndian(ntpTicks);
531
532 if (m_clientTimingSocket->writeDatagram((char *)req.data(), req.size(), m_peerAddress,
533 m_clientTimingPort) != (qint64)req.size())
534 {
535 LOG(VB_PLAYBACK, LOG_ERR, LOC + "Failed to send resend time request.");
536 return;
537 }
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')));
541}
542
550{
551 const char *req = buf.constData();
552
553 uint32_t ntpSec = qFromBigEndian(*(uint32_t *)(req + 8));
554 uint32_t ntpTicks = qFromBigEndian(*(uint32_t *)(req + 12));
555 auto time1 = NTPToLocal(ntpSec, ntpTicks);
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')));
559 // network latency equal time difference in ms between request and response
560 // divide by two for approximate time of one way trip
561 m_networkLatency = (time2 - time1) / 2;
562 LOG(VB_AUDIO, LOG_DEBUG, LOC + QString("Network Latency: %1ms")
563 .arg(m_networkLatency.count()));
564
565 // now calculate the time difference between the client and us.
566 // this is NTP time, where sec is in seconds, and ticks is in 1/2^32s
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')));
571
572 // convert ticks into ms
573 std::chrono::milliseconds master = NTPToLocal(sec, ticks);
574
575 // This value is typically huge (~50 years) and meaningless as
576 // Apple products send uptime, not wall time.
577 m_clockSkew = master - time2;
578}
579
580
581// Timestamps in Airplay packets are stored using the format designed
582// for NTP: a 32-bit seconds field and a 32-bit fractional seconds
583// field, based on a zero value meaning January 1st 1900. This
584// constant handles the conversion between that epoch base, and the
585// unix epoch base time of January 1st, 1970.
586static constexpr uint64_t CLOCK_EPOCH {0x83aa7e80};
587std::chrono::milliseconds MythRAOPConnection::NTPToLocal(uint32_t sec, uint32_t ticks)
588{
589 return std::chrono::milliseconds((((int64_t)sec - CLOCK_EPOCH) * 1000LL) +
590 (((int64_t)ticks * 1000LL) >> 32));
591}
592void MythRAOPConnection::microsecondsToNTP(std::chrono::microseconds usec,
593 uint32_t &ntpSec, uint32_t &ntpTicks)
594{
595 ntpSec = duration_cast<std::chrono::seconds>(usec).count() + CLOCK_EPOCH;
596 ntpTicks = ((usec % 1s).count() << 32) / 1000000;
597}
598void MythRAOPConnection::timevalToNTP(timeval t, uint32_t &ntpSec, uint32_t &ntpTicks)
599{
600 auto micros = durationFromTimeval<std::chrono::microseconds>(t);
601 microsecondsToNTP(micros, ntpSec, ntpTicks);
602}
603
604
614bool MythRAOPConnection::GetPacketType(const QByteArray &buf, uint8_t &type,
615 uint16_t &seq, uint64_t &timestamp)
616{
617 // All RAOP packets start with | 0x80/0x90 (first sync) | PACKET_TYPE |
618 if ((uint8_t)buf[0] != 0x80 && (uint8_t)buf[0] != 0x90)
619 {
620 return false;
621 }
622
623 type = buf[1];
624 // Is it first sync packet?
625 if ((uint8_t)buf[0] == 0x90 && type == FIRSTSYNC)
626 {
627 return true;
628 }
629 if (type != FIRSTAUDIO_DATA)
630 {
631 type &= ~0x80;
632 }
633
635 return true;
636
637 const char *ptr = buf.constData();
638 if (type == AUDIO_RESEND)
639 {
640 ptr += 4;
641 }
642 seq = qFromBigEndian(*(uint16_t *)(ptr + 2));
643 timestamp = qFromBigEndian(*(uint32_t *)(ptr + 4));
644 return true;
645}
646
647// Audio decode / playback related routines
648
650 const QByteArray *buf,
651 QList<AudioData> *dest)
652{
653 const char *data_in = buf->constData();
654 int len = buf->size();
655 if (type == AUDIO_RESEND)
656 {
657 data_in += 4;
658 len -= 4;
659 }
660 data_in += 12;
661 len -= 12;
662 if (len < 16)
663 return -1;
664
665 int aeslen = len & ~0xf;
666 int outlen1 {0};
667 int outlen2 {0};
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);
671 if (
672#if OPENSSL_VERSION_NUMBER < 0x030000000L
673 // Debian < 12, Fedora < 36, RHEL < 9, SuSe 15, Ubuntu < 2204
674 EVP_DecryptInit_ex(m_cctx, m_cipher, nullptr, m_sessionKey.data(),
675 reinterpret_cast<const uint8_t *>(m_aesIV.data()))
676#else
677 EVP_DecryptInit_ex2(m_cctx, m_cipher, m_sessionKey.data(),
678 reinterpret_cast<const uint8_t *>(m_aesIV.data()),
679 nullptr)
680#endif
681 != 1)
682 {
683 LOG(VB_PLAYBACK, LOG_WARNING, LOC +
684 QString("EVP_DecryptInit_ex failed. (%1)")
685 .arg(ERR_error_string(ERR_get_error(), nullptr)));
686 }
687 else if (EVP_DecryptUpdate(m_cctx, decrypted_data.data(), &outlen1,
688 reinterpret_cast<const uint8_t *>(data_in),
689 aeslen) != 1)
690 {
691 LOG(VB_PLAYBACK, LOG_WARNING, LOC +
692 QString("EVP_DecryptUpdate failed. (%1)")
693 .arg(ERR_error_string(ERR_get_error(), nullptr)));
694 }
695 else if (EVP_DecryptFinal_ex(m_cctx, decrypted_data.data() + outlen1, &outlen2) != 1)
696 {
697 LOG(VB_PLAYBACK, LOG_WARNING, LOC +
698 QString("EVP_DecryptFinal_ex failed. (%1)")
699 .arg(ERR_error_string(ERR_get_error(), nullptr)));
700 }
701 std::copy(data_in + aeslen, data_in + len,
702 decrypted_data.data() + aeslen);
703
704 AVCodecContext *ctx = m_codecContext;
705 AVPacket *tmp_pkt = av_packet_alloc();
706 if (tmp_pkt == nullptr)
707 return -1;
708 tmp_pkt->data = decrypted_data.data();
709 tmp_pkt->size = len;
710
711 uint32_t frames_added = 0;
712 auto *samples = (uint8_t *)av_mallocz(AudioOutput::kMaxSizeBuffer);
713 while (tmp_pkt->size > 0)
714 {
715 int data_size = 0;
716 int ret = m_audio->DecodeAudio(ctx, samples, data_size, tmp_pkt);
717 if (ret < 0)
718 {
719 av_free(samples);
720 return -1;
721 }
722
723 if (data_size)
724 {
725 int num_samples = data_size /
726 (ctx->ch_layout.nb_channels * av_get_bytes_per_sample(ctx->sample_fmt));
727
728 frames_added += num_samples;
729 AudioData block {samples, data_size, num_samples};
730 dest->append(block);
731 }
732 tmp_pkt->data += ret;
733 tmp_pkt->size -= ret;
734 }
735 av_packet_free(&tmp_pkt);
736 return frames_added;
737}
738
740{
742 return;
743
744 if (m_audio->IsPaused())
745 {
746 // ALSA takes a while to unpause, enough to have SYNC starting to drop
747 // packets, so unpause as early as possible
748 m_audio->Pause(false);
749 }
750 auto dtime = nowAsDuration<std::chrono::milliseconds>() - m_timeLastSync;
751 auto rtp = dtime + m_currentTimestamp;
752 auto buffered = m_audioStarted ? m_audio->GetAudioBufferedTime() : 0ms;
753
754 // Keep audio framework buffer as short as possible, keeping everything in
755 // m_audioQueue, so we can easily reset the least amount possible
756 if (buffered > AUDIOCARD_BUFFER)
757 return;
758
759 // Also make sure m_audioQueue never goes to less than 1/3 of the RDP stream
760 // total latency, this should gives us enough time to receive missed packets
761 std::chrono::milliseconds queue = framesToMs((uint64_t)m_audioQueue.size() * m_framesPerPacket);
762 if (queue < m_bufferLength / 3)
763 return;
764
765 rtp += buffered;
766
767 // How many packets to add to the audio card, to fill AUDIOCARD_BUFFER
768 int max_packets = ((AUDIOCARD_BUFFER - buffered).count()
769 * m_frameRate / 1000) / m_framesPerPacket;
770 int i = 0;
771 std::chrono::milliseconds timestamp = 0ms;
772
773 QMapIterator<std::chrono::milliseconds,AudioPacket> packet_it(m_audioQueue);
774 while (packet_it.hasNext() && i <= max_packets)
775 {
776 packet_it.next();
777
778 timestamp = packet_it.key();
779 if (timestamp < rtp)
780 {
781 if (!m_audioStarted)
782 {
783 m_audio->Reset(); // clear audio card
784 }
785 AudioPacket frames = packet_it.value();
786
787 if (m_lastSequence != frames.seq)
788 {
789 LOG(VB_PLAYBACK, LOG_ERR, LOC +
790 QString("Audio discontinuity seen. Played %1 (%3) expected %2")
791 .arg(frames.seq).arg(m_lastSequence).arg(timestamp.count()));
792 m_lastSequence = frames.seq;
793 }
795
796 for (const auto & data : std::as_const(*frames.data))
797 {
798 int offset = 0;
799 int framecnt = 0;
800
801 if (m_adjustedLatency > 0ms)
802 {
803 // calculate how many frames we have to drop to catch up
804 offset = (m_adjustedLatency.count() * m_frameRate / 1000) *
806 offset = std::min(offset, data.length);
807 framecnt = offset / m_audio->GetBytesPerFrame();
808 m_adjustedLatency -= framesToMs(framecnt+1);
809 LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
810 QString("ProcessAudio: Dropping %1 frames to catch up "
811 "(%2ms to go)")
812 .arg(framecnt).arg(m_adjustedLatency.count()));
813 timestamp += framesToMs(framecnt);
814 }
815 m_audio->AddData((char *)data.data + offset,
816 data.length - offset,
817 std::chrono::milliseconds(timestamp), framecnt);
818 timestamp += m_audio->LengthLastData();
819 }
820 i++;
821 m_audioStarted = true;
822 }
823 else
824 {
825 // QMap is sorted, so no need to continue if not found
826 break;
827 }
828 }
829
830 ExpireAudio(timestamp);
831 m_lastTimestamp = timestamp;
832
833 // restart audio timer should we stop receiving data on regular interval,
834 // we need to continue processing the audio queue
836}
837
838int MythRAOPConnection::ExpireAudio(std::chrono::milliseconds timestamp)
839{
840 int res = 0;
841 QMutableMapIterator<std::chrono::milliseconds,AudioPacket> packet_it(m_audioQueue);
842 while (packet_it.hasNext())
843 {
844 packet_it.next();
845 if (packet_it.key() < timestamp)
846 {
847 AudioPacket frames = packet_it.value();
848 if (frames.data)
849 {
850 for (const auto & data : std::as_const(*frames.data))
851 av_free(data.data);
852 delete frames.data;
853 }
854 m_audioQueue.remove(packet_it.key());
855 res++;
856 }
857 }
858 return res;
859}
860
862{
863 if (m_audio)
864 {
865 m_audio->Reset();
866 }
867 ExpireAudio(std::chrono::milliseconds::max());
868 ExpireResendRequests(std::chrono::milliseconds::max());
869 m_audioStarted = false;
870}
871
873{
874 LOG(VB_PLAYBACK, LOG_INFO, LOC + "Closing connection after inactivity.");
875 m_socket->disconnectFromHost();
876}
877
879{
880 if (!m_audio && OpenAudioDevice())
881 {
883 }
884
886 {
888 }
889}
890
896{
897 auto *socket = qobject_cast<QTcpSocket *>(sender());
898 if (!socket)
899 return;
900
901 QByteArray data = socket->readAll();
902 if (VERBOSE_LEVEL_CHECK(VB_PLAYBACK, LOG_DEBUG))
903 {
904 QByteArray printable = data.left(32);
905 LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("readClient(%1): %2%3")
906 .arg(data.size())
907 .arg(printable.toHex().toUpper().data(),
908 data.size() > 32 ? "..." : ""));
909 }
910 // For big content, we may be called several times for a single packet
912 {
913 m_incomingHeaders.clear();
914 m_incomingContent.clear();
915 m_incomingSize = 0;
916
917 QTextStream stream(data);
918 QString line = stream.readLine();
919 while (!line.isEmpty())
920 {
921 LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("Header(%1) = %2")
922 .arg(m_socket->peerAddress().toString(), line));
923 m_incomingHeaders.append(line);
924 if (line.contains("Content-Length:"))
925 {
926 m_incomingSize = line.mid(line.indexOf(" ") + 1).toInt();
927 }
928 line = stream.readLine();
929 }
930
931 if (m_incomingHeaders.empty())
932 return;
933
934 if (!stream.atEnd())
935 {
936 int pos = stream.pos();
937 if (pos > 0)
938 {
939 m_incomingContent.append(data.mid(pos));
940 }
941 }
942 }
943 else
944 {
945 m_incomingContent.append(data);
946 }
947
948 // If we haven't received all the content yet, wait (see when receiving
949 // coverart
951 {
952 m_incomingPartial = true;
953 return;
954 }
955 m_incomingPartial = false;
956 LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("Content(%1) = %2")
957 .arg(m_incomingContent.size()).arg(m_incomingContent.constData()));
958
960}
961
962void MythRAOPConnection::ProcessRequest(const QStringList &header,
963 const QByteArray &content)
964{
965 if (header.isEmpty())
966 return;
967
968 RawHash tags = FindTags(header);
969
970 if (!tags.contains("CSeq"))
971 {
972 LOG(VB_PLAYBACK, LOG_ERR, LOC + "ProcessRequest: Didn't find CSeq");
973 return;
974 }
975
976 QString option = header[0].left(header[0].indexOf(" "));
977
978 // process RTP-info field
979 bool gotRTP = false;
980 uint16_t RTPseq = 0;
981 uint64_t RTPtimestamp = 0;
982 if (tags.contains("RTP-Info"))
983 {
984 gotRTP = true;
985 QString data = tags["RTP-Info"];
986 QStringList items = data.split(";");
987 for (const QString& item : std::as_const(items))
988 {
989 if (item.startsWith("seq"))
990 {
991 RTPseq = item.mid(item.indexOf("=") + 1).trimmed().toUShort();
992 }
993 else if (item.startsWith("rtptime"))
994 {
995 RTPtimestamp = item.mid(item.indexOf("=") + 1).trimmed().toUInt();
996 }
997 }
998 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("RTP-Info: seq=%1 rtptime=%2")
999 .arg(RTPseq).arg(RTPtimestamp));
1000 }
1001
1002 if (gCoreContext->GetBoolSetting("AirPlayPasswordEnabled", false))
1003 {
1004 if (m_nonce.isEmpty())
1005 {
1007 }
1008 if (!tags.contains("Authorization"))
1009 {
1010 // 60 seconds to enter password.
1011 m_watchdogTimer->start(1min);
1013 return;
1014 }
1015
1016 QByteArray auth;
1017 if (DigestMd5Response(tags["Authorization"], option, m_nonce,
1018 gCoreContext->GetSetting("AirPlayPassword"),
1019 auth) == auth)
1020 {
1021 LOG(VB_PLAYBACK, LOG_INFO, LOC + "RAOP client authenticated");
1022 }
1023 else
1024 {
1025 LOG(VB_PLAYBACK, LOG_INFO, LOC + "RAOP authentication failed");
1027 return;
1028 }
1029 }
1030 *m_textStream << "RTSP/1.0 200 OK\r\n";
1031
1032 if (tags.contains("Apple-Challenge"))
1033 {
1034 LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("Received Apple-Challenge"));
1035
1036 *m_textStream << "Apple-Response: ";
1037 if (!LoadKey())
1038 return;
1039 size_t tosize = EVP_PKEY_size(g_devPrivKey);
1040 std::vector<uint8_t>to;
1041 to.resize(tosize);
1042
1043 QByteArray challenge =
1044 QByteArray::fromBase64(tags["Apple-Challenge"].toLatin1());
1045 int challenge_size = challenge.size();
1046 if (challenge_size != 16)
1047 {
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);
1052 }
1053
1054 int i = 0;
1055 std::array<uint8_t,38> from {};
1056 std::copy(challenge.cbegin(), challenge.cbegin() + challenge_size,
1057 from.data());
1058 i += challenge_size;
1059 if (m_socket->localAddress().protocol() ==
1060 QAbstractSocket::IPv4Protocol)
1061 {
1062 uint32_t ip = m_socket->localAddress().toIPv4Address();
1063 ip = qToBigEndian(ip);
1064 memcpy(&from[i], &ip, 4);
1065 i += 4;
1066 }
1067 else if (m_socket->localAddress().protocol() ==
1068 QAbstractSocket::IPv6Protocol)
1069 {
1070 Q_IPV6ADDR ip = m_socket->localAddress().toIPv6Address();
1071 if(memcmp(&ip,
1072 "\x00\x00\x00\x00" "\x00\x00\x00\x00" "\x00\x00\xff\xff",
1073 12) == 0)
1074 {
1075 memcpy(&from[i], &ip[12], 4);
1076 i += 4;
1077 }
1078 else
1079 {
1080 memcpy(&from[i], &ip, 16);
1081 i += 16;
1082 }
1083 }
1085 {
1086 memcpy(&from[i], m_hardwareId.constData(), AIRPLAY_HARDWARE_ID_SIZE);
1088 }
1089 else
1090 {
1091 LOG(VB_PLAYBACK, LOG_ERR, LOC +
1092 QString("Hardware MAC address size %1, expected %2")
1093 .arg(m_hardwareId.size()).arg(AIRPLAY_HARDWARE_ID_SIZE));
1094 }
1095
1096 int pad = 32 - i;
1097 if (pad > 0)
1098 {
1099 memset(&from[i], 0, pad);
1100 i += pad;
1101 }
1102
1103 LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1104 QString("Full base64 response: '%1' size %2")
1105 .arg(QByteArray((char *)from.data(), i).toBase64().constData())
1106 .arg(i));
1107
1108 auto *pctx = EVP_PKEY_CTX_new(g_devPrivKey, nullptr);
1109 if (nullptr == pctx)
1110 {
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)));
1114 }
1115 else if (EVP_PKEY_sign_init(pctx) <= 0)
1116 {
1117 LOG(VB_PLAYBACK, LOG_WARNING, LOC +
1118 QString("EVP_PKEY_sign_init failed. (%1)")
1119 .arg(ERR_error_string(ERR_get_error(), nullptr)));
1120 }
1121 else if (EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PADDING) <= 0)
1122 {
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)));
1126 }
1127 else if (EVP_PKEY_sign(pctx, to.data(), &tosize,
1128 from.data(), i) <= 0)
1129 {
1130 LOG(VB_PLAYBACK, LOG_WARNING, LOC +
1131 QString("EVP_PKEY_sign failed. (%1)")
1132 .arg(ERR_error_string(ERR_get_error(), nullptr)));
1133 }
1134
1135 QByteArray base64 = QByteArray((const char *)to.data(), tosize).toBase64();
1136
1137 for (int pos = base64.size() - 1; pos > 0; pos--)
1138 {
1139 if (base64[pos] == '=')
1140 base64[pos] = ' ';
1141 else
1142 break;
1143 }
1144 LOG(VB_PLAYBACK, LOG_DEBUG, QString("tSize=%1 tLen=%2 tResponse=%3")
1145 .arg(tosize).arg(base64.size()).arg(base64.constData()));
1146 *m_textStream << base64.trimmed() << "\r\n";
1147 }
1148
1149 QString responseData;
1150 if (option == "OPTIONS")
1151 {
1152 *m_textStream << "Public: ANNOUNCE, SETUP, RECORD, PAUSE, FLUSH, "
1153 "TEARDOWN, OPTIONS, GET_PARAMETER, SET_PARAMETER, POST, GET\r\n";
1154 }
1155 else if (option == "ANNOUNCE")
1156 {
1157 QStringList lines = splitLines(content);
1158 for (const QString& line : std::as_const(lines))
1159 {
1160 if (line.startsWith("a=rsaaeskey:"))
1161 {
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()));
1167
1168 if (LoadKey())
1169 {
1170 size_t size = EVP_PKEY_get_size(g_devPrivKey);
1171 auto *pctx = EVP_PKEY_CTX_new(g_devPrivKey /* EVP_PKEY *pkey */,
1172 nullptr /* ENGINE *e */);
1173 m_sessionKey.resize(size);
1174 size_t size_out {size};
1175 if (nullptr == pctx)
1176 {
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)));
1180 }
1181 else if (EVP_PKEY_decrypt_init(pctx) <= 0)
1182 {
1183 LOG(VB_PLAYBACK, LOG_WARNING, LOC + QString("EVP_PKEY_decrypt_init failed. (%1)")
1184 .arg(ERR_error_string(ERR_get_error(), nullptr)));
1185 }
1186 else if (EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_OAEP_PADDING) <= 0)
1187 {
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)));
1191 }
1192 else if (EVP_PKEY_decrypt(pctx, m_sessionKey.data(), &size_out,
1193 (const unsigned char *)decodedkey.constData(), decodedkey.size()) > 0)
1194 {
1195 // SUCCESS
1196 LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1197 "Successfully decrypted AES key from RSA.");
1198 }
1199 else
1200 {
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)));
1204 }
1205 }
1206 }
1207 else if (line.startsWith("a=aesiv:"))
1208 {
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()));
1214 }
1215 else if (line.startsWith("a=fmtp:"))
1216 {
1217 m_audioFormat.clear();
1218 QString format = line.mid(7).trimmed();
1219 QList<QString> formats = format.split(' ');
1220 for (const QString& fmt : std::as_const(formats))
1221 m_audioFormat.append(fmt.toInt());
1222
1223 for (int fmt : std::as_const(m_audioFormat))
1224 LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1225 QString("Audio parameter: %1").arg(fmt));
1230 }
1231 }
1232 }
1233 else if (option == "SETUP")
1234 {
1235 if (tags.contains("Transport"))
1236 {
1237 // New client is trying to play audio, disconnect all the other clients
1238 auto *dev = qobject_cast<MythRAOPDevice*>(parent());
1239 if (dev != nullptr)
1240 dev->DeleteAllClients(this);
1241 gCoreContext->WantingPlayback(parent());
1242 m_playbackStarted = true;
1243
1244 int control_port = 0;
1245 int timing_port = 0;
1246 QString data = tags["Transport"];
1247 QStringList items = data.split(";");
1248 bool events = false;
1249
1250 for (const QString& item : std::as_const(items))
1251 {
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"))
1257 events = true;
1258 }
1259
1260 LOG(VB_PLAYBACK, LOG_INFO, LOC +
1261 QString("Negotiated setup with client %1 on port %2")
1262 .arg(m_socket->peerAddress().toString())
1263 .arg(m_socket->peerPort()));
1264 LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1265 QString("control port: %1 timing port: %2")
1266 .arg(control_port).arg(timing_port));
1267
1268 m_peerAddress = m_socket->peerAddress();
1269
1271 {
1272 m_clientControlSocket->disconnect();
1274 delete m_clientControlSocket;
1275 }
1276
1278 int controlbind_port =
1281 if (controlbind_port < 0)
1282 {
1283 LOG(VB_PLAYBACK, LOG_ERR, LOC +
1284 QString("Failed to bind to client control port. "
1285 "Control of audio stream may fail"));
1286 }
1287 else
1288 {
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));
1292 }
1293 m_clientControlPort = control_port;
1294 connect(m_clientControlSocket,
1296 this,
1298
1300 {
1301 m_clientTimingSocket->disconnect();
1303 delete m_clientTimingSocket;
1304 }
1305
1306 m_clientTimingSocket = new ServerPool(this);
1307 int timingbind_port =
1310 if (timingbind_port < 0)
1311 {
1312 LOG(VB_PLAYBACK, LOG_ERR, LOC +
1313 QString("Failed to bind to client timing port. "
1314 "Timing of audio stream will be incorrect"));
1315 }
1316 else
1317 {
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));
1321 }
1322 m_clientTimingPort = timing_port;
1323 connect(m_clientTimingSocket,
1325 this,
1327
1328 if (m_eventServer)
1329 {
1330 // Should never get here, but just in case
1331 for (auto *client : std::as_const(m_eventClients))
1332 {
1333 client->disconnect();
1334 client->abort();
1335 delete client;
1336 }
1337 m_eventClients.clear();
1338 m_eventServer->disconnect();
1340 delete m_eventServer;
1341 m_eventServer = nullptr;
1342 }
1343
1344 if (events)
1345 {
1346 m_eventServer = new ServerPool(this);
1349 if (m_eventPort < 0)
1350 {
1351 LOG(VB_PLAYBACK, LOG_ERR, LOC +
1352 "Failed to find a port for RAOP events server.");
1353 }
1354 else
1355 {
1356 LOG(VB_PLAYBACK, LOG_INFO, LOC +
1357 QString("Listening for RAOP events on port %1").arg(m_eventPort));
1360 }
1361 }
1362
1363 if (OpenAudioDevice())
1364 {
1365 CreateDecoder();
1366 // Calculate audio card latency
1367 if (m_cardLatency < 0ms)
1368 {
1370 // if audio isn't started, start playing 500ms worth of silence
1371 // and measure timestamp difference
1372 LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1373 QString("Audio hardware latency: %1ms")
1374 .arg((m_cardLatency + m_networkLatency).count()));
1375 }
1376 }
1377
1378 // Recreate transport line with new ports value
1379 QString newdata;
1380 bool first = true;
1381 for (const QString& item : std::as_const(items))
1382 {
1383 if (!first)
1384 {
1385 newdata += ";";
1386 }
1387 if (item.startsWith("control_port"))
1388 {
1389 newdata += "control_port=" + QString::number(controlbind_port);
1390 }
1391 else if (item.startsWith("timing_port"))
1392 {
1393 newdata += "timing_port=" + QString::number(timingbind_port);
1394 }
1395 else if (item.startsWith("events"))
1396 {
1397 newdata += "event_port=" + QString::number(m_eventPort);
1398 }
1399 else
1400 {
1401 newdata += item;
1402 }
1403 first = false;
1404 }
1405 if (!first)
1406 {
1407 newdata += ";";
1408 }
1409 newdata += "server_port=" + QString::number(m_dataPort);
1410
1411 *m_textStream << "Transport: " << newdata << "\r\n";
1412 *m_textStream << "Session: 1\r\n";
1413 *m_textStream << "Audio-Jack-Status: connected\r\n";
1414
1415 // Ask for master clock value to determine time skew and average network latency
1417 }
1418 else
1419 {
1420 LOG(VB_PLAYBACK, LOG_ERR, LOC +
1421 "No Transport details found - Ignoring");
1422 }
1423 }
1424 else if (option == "RECORD")
1425 {
1426 if (gotRTP)
1427 {
1428 m_nextSequence = RTPseq;
1429 m_nextTimestamp = framesToMs(RTPtimestamp);
1430 }
1431
1432 // Calculate audio card latency
1433 if (m_cardLatency > 0ms)
1434 {
1435 *m_textStream << QString("Audio-Latency: %1")
1436 .arg((m_cardLatency+m_networkLatency).count());
1437 }
1438 }
1439 else if (option == "FLUSH")
1440 {
1441 if (gotRTP)
1442 {
1443 m_nextSequence = RTPseq;
1444 m_nextTimestamp = framesToMs(RTPtimestamp);
1446 }
1447 // determine RTP timestamp of last sample played
1448 std::chrono::milliseconds timestamp = m_audioStarted && m_audio ?
1450 *m_textStream << "RTP-Info: rtptime=" << QString::number(MsToFrame(timestamp));
1451 m_streamingStarted = false;
1452 ResetAudio();
1453 }
1454 else if (option == "SET_PARAMETER")
1455 {
1456 if (tags.contains("Content-Type"))
1457 {
1458 if (tags["Content-Type"] == "text/parameters")
1459 {
1460 QString name = content.left(content.indexOf(":"));
1461 QString param = content.mid(content.indexOf(":") + 1).trimmed();
1462
1463 LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1464 QString("text/parameters: name=%1 parem=%2")
1465 .arg(name, param));
1466
1467 if (name == "volume" && m_allowVolumeControl && m_audio)
1468 {
1469 float volume = (param.toFloat() + 30.0F) * 100.0F / 30.0F;
1470 if (volume < 0.01F)
1471 volume = 0.0F;
1472 LOG(VB_PLAYBACK, LOG_INFO,
1473 LOC + QString("Setting volume to %1 (raw %3)")
1474 .arg(volume).arg(param));
1475 m_audio->SetCurrentVolume((int)volume);
1476 }
1477 else if (name == "progress")
1478 {
1479 QStringList items = param.split("/");
1480 if (items.size() == 3)
1481 {
1482 m_progressStart = items[0].toUInt();
1483 m_progressCurrent = items[1].toUInt();
1484 m_progressEnd = items[2].toUInt();
1485 }
1486 int length =
1488 int current =
1490
1491 LOG(VB_PLAYBACK, LOG_INFO,
1492 LOC +QString("Progress: %1/%2")
1494 stringFromSeconds(length)));
1495 SendNotification(true);
1496 }
1497 }
1498 else if(tags["Content-Type"] == "image/none")
1499 {
1500 m_artwork.clear();
1501 SendNotification(false);
1502 }
1503 else if(tags["Content-Type"].startsWith("image/"))
1504 {
1505 // Receiving image coverart
1507 SendNotification(false);
1508 }
1509 else if (tags["Content-Type"] == "application/x-dmap-tagged")
1510 {
1511 // Receiving DMAP metadata
1513 LOG(VB_PLAYBACK, LOG_INFO,
1514 QString("Receiving Title:%1 Artist:%2 Album:%3 Format:%4")
1515 .arg(m_dmap["minm"], m_dmap["asar"],
1516 m_dmap["asal"], m_dmap["asfm"]));
1517 SendNotification(false);
1518 }
1519 }
1520 }
1521 else if (option == "GET_PARAMETER")
1522 {
1523 if (tags.contains("Content-Type"))
1524 {
1525 if (tags["Content-Type"] == "text/parameters")
1526 {
1527 QStringList lines = splitLines(content);
1528 *m_textStream << "Content-Type: text/parameters\r\n";
1529 for (const QString& line : std::as_const(lines))
1530 {
1531 if (line == "volume")
1532 {
1533 int volume = (m_allowVolumeControl && m_audio) ?
1535 responseData += QString("volume: %1\r\n")
1536 .arg((volume * 30.0F / 100.0F) - 30.0F,1,'f',6,'0');
1537 }
1538 }
1539 }
1540 }
1541 }
1542 else if (option == "TEARDOWN")
1543 {
1544 m_socket->disconnectFromHost();
1545 return;
1546 }
1547 else
1548 {
1549 LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("Command not handled: %1")
1550 .arg(option));
1551 }
1552 FinishResponse(m_textStream, m_socket, option, tags["CSeq"], responseData);
1553}
1554
1556 QTcpSocket *socket,
1557 QString &cseq)
1558{
1559 if (!stream)
1560 return;
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";
1566 *stream << "\r\n";
1567 stream->flush();
1568 LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1569 QString("Finished Authentication request %2, Send: %3")
1570 .arg(cseq).arg(socket->flush()));
1571}
1572
1573void MythRAOPConnection::FinishResponse(RaopNetStream *stream, QTcpSocket *socket,
1574 QString &option, QString &cseq, QString &responseData)
1575{
1576 if (!stream)
1577 return;
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;
1582 else
1583 *stream << "\r\n";
1584 stream->flush();
1585 LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("Finished %1 %2 , Send: %3")
1586 .arg(option, cseq, QString::number(socket->flush())));
1587}
1588
1595{
1596 static QMutex s_lock;
1597 QMutexLocker locker(&s_lock);
1598
1599 if (g_devPrivKey)
1600 return true;
1601
1602 QString sName( "/RAOPKey.rsa" );
1603 FILE *file = fopen(GetConfDir().toUtf8() + sName.toUtf8(), "rb");
1604
1605 if ( !file )
1606 {
1607 g_rsaLastError = tr("Failed to read key from: %1").arg(GetConfDir() + sName);
1608 LOG(VB_PLAYBACK, LOG_ERR, LOC + g_rsaLastError);
1609 return false;
1610 }
1611
1612 g_devPrivKey = PEM_read_PrivateKey(file, nullptr, nullptr, nullptr);
1613 fclose(file);
1614
1615 if (g_devPrivKey)
1616 {
1617 int id = EVP_PKEY_get_id(g_devPrivKey);
1618 int type = EVP_PKEY_type(id);
1619 if (type != EVP_PKEY_RSA)
1620 {
1621 g_rsaLastError = tr("Key is not a RSA private key.");
1622 EVP_PKEY_free(g_devPrivKey);
1623 g_devPrivKey = nullptr;
1624 LOG(VB_PLAYBACK, LOG_ERR, LOC + g_rsaLastError);
1625 return false;
1626 }
1627 g_rsaLastError = "";
1628 LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "Loaded RSA private key");
1629 return true;
1630 }
1631
1632 g_rsaLastError = tr("Failed to load RSA private key.");
1633 LOG(VB_PLAYBACK, LOG_ERR, LOC + g_rsaLastError);
1634 return false;
1635}
1636
1637RawHash MythRAOPConnection::FindTags(const QStringList &lines)
1638{
1639 RawHash result;
1640 if (lines.isEmpty())
1641 return result;
1642
1643 for (const QString& line : std::as_const(lines))
1644 {
1645 int index = line.indexOf(":");
1646 if (index > 0)
1647 {
1648 result.insert(line.left(index).trimmed(),
1649 line.mid(index + 1).trimmed());
1650 }
1651 }
1652 return result;
1653}
1654
1655QStringList MythRAOPConnection::splitLines(const QByteArray &lines)
1656{
1657 QStringList list;
1658 QTextStream stream(lines);
1659
1660 QString line = stream.readLine();
1661 while (!line.isEmpty())
1662 {
1663 list.append(line);
1664 line = stream.readLine();
1665 }
1666
1667 return list;
1668}
1669
1678{
1679 QTime time = QTime(0,0).addSecs(timeInSeconds);
1680 return time.toString(time.hour() > 0 ? "HH:mm:ss" : "mm:ss");
1681}
1682
1688std::chrono::milliseconds MythRAOPConnection::framesToMs(uint64_t frames) const
1689{
1690 return std::chrono::milliseconds((frames * 1000ULL) / m_frameRate);
1691}
1692
1693uint64_t MythRAOPConnection::MsToFrame(std::chrono::milliseconds millis) const
1694{
1695 return millis.count() * m_frameRate / 1000ULL;
1696}
1697
1705QMap<QString,QString> MythRAOPConnection::decodeDMAP(const QByteArray &dmap)
1706{
1707 QMap<QString,QString> result;
1708 int offset = 8;
1709 while (offset < dmap.size())
1710 {
1711 QString tag = dmap.mid(offset, 4);
1712 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());
1717 offset += length;
1718 result.insert(tag, content);
1719 }
1720 return result;
1721}
1722
1724{
1726
1727 // create an ALAC decoder
1728 m_codec = avcodec_find_decoder(AV_CODEC_ID_ALAC);
1729 if (!m_codec)
1730 {
1731 LOG(VB_PLAYBACK, LOG_ERR, LOC
1732 + "Failed to create ALAC decoder- going silent...");
1733 return false;
1734 }
1735
1736 m_codecContext = avcodec_alloc_context3(m_codec);
1737 if (m_codecContext)
1738 {
1739 auto *extradata = new unsigned char[36];
1740 memset(extradata, 0, 36);
1741 if (m_audioFormat.size() < 12)
1742 {
1743 LOG(VB_PLAYBACK, LOG_ERR, LOC +
1744 "Creating decoder but haven't seen audio format.");
1745 }
1746 else
1747 {
1748 uint32_t fs = m_audioFormat[1]; // frame size
1749 extradata[12] = (fs >> 24) & 0xff;
1750 extradata[13] = (fs >> 16) & 0xff;
1751 extradata[14] = (fs >> 8) & 0xff;
1752 extradata[15] = fs & 0xff;
1753 extradata[16] = m_channels; // channels
1754 extradata[17] = m_audioFormat[3]; // sample size
1755 extradata[18] = m_audioFormat[4]; // rice_historymult
1756 extradata[19] = m_audioFormat[5]; // rice_initialhistory
1757 extradata[20] = m_audioFormat[6]; // rice_kmodifier
1758 }
1759 m_codecContext->extradata = extradata;
1760 m_codecContext->extradata_size = 36;
1761 m_codecContext->ch_layout.nb_channels = m_channels;
1762 if (avcodec_open2(m_codecContext, m_codec, nullptr) < 0)
1763 {
1764 LOG(VB_PLAYBACK, LOG_ERR, LOC +
1765 "Failed to open ALAC decoder - going silent...");
1767 return false;
1768 }
1769 LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "Opened ALAC decoder.");
1770 m_codecContext->sample_rate = m_audioFormat[11]; // sampleRate
1771 }
1772
1773 return true;
1774}
1775
1777{
1778 if (m_codecContext)
1779 {
1780 avcodec_free_context(&m_codecContext);
1781 }
1782 m_codec = nullptr;
1783}
1784
1786{
1788
1789 QString passthru = gCoreContext->GetBoolSetting("PassThruDeviceOverride", false)
1790 ? gCoreContext->GetSetting("PassThruOutputDevice") : QString();
1791 QString device = gCoreContext->GetSetting("AudioOutputDevice");
1792
1794 AV_CODEC_ID_NONE, m_frameRate, AUDIOOUTPUT_MUSIC,
1795 m_allowVolumeControl, false);
1796 if (m_audio == nullptr || !m_audio->isConfigured())
1797 {
1798 LOG(VB_PLAYBACK, LOG_ERR, LOC +
1799 "Failed to open audio device. Going silent...");
1802 return false;
1803 }
1804
1806 LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "Opened audio device.");
1807 return true;
1808}
1809
1811{
1812 delete m_audio;
1813 m_audio = nullptr;
1814}
1815
1817{
1818 if (m_audioTimer)
1819 return;
1820
1821 m_audioTimer = new QTimer();
1823 m_audioTimer->start(5s);
1824}
1825
1827{
1828 if (m_audioTimer)
1829 {
1830 m_audioTimer->stop();
1831 }
1832 delete m_audioTimer;
1833 m_audioTimer = nullptr;
1834}
1835
1840std::chrono::milliseconds MythRAOPConnection::AudioCardLatency(void)
1841{
1842 if (!m_audio)
1843 return 0ms;
1844
1845 auto *samples = (int16_t *)av_mallocz(AudioOutput::kMaxSizeBuffer);
1846 int frames = AUDIOCARD_BUFFER.count() * m_frameRate / 1000;
1847 m_audio->AddData((char *)samples,
1848 frames * (m_sampleSize>>3) * m_channels,
1849 0ms,
1850 frames);
1851 av_free(samples);
1852 usleep(duration_cast<std::chrono::microseconds>(AUDIOCARD_BUFFER).count());
1853 std::chrono::milliseconds audiots = m_audio->GetAudiotime();
1854 LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("AudioCardLatency: ts=%1ms")
1855 .arg(audiots.count()));
1856 return AUDIOCARD_BUFFER - audiots;
1857}
1858
1860{
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()));
1864
1865 m_eventClients.append(client);
1866 connect(client, &QAbstractSocket::disconnected, this, &MythRAOPConnection::deleteEventClient);
1867}
1868
1870{
1871 auto *client = qobject_cast<QTcpSocket *>(sender());
1872 QString label;
1873
1874 if (client != nullptr)
1875 label = QString("%1:%2").arg(client->peerAddress().toString()).arg(client->peerPort());
1876 else
1877 label = QString("unknown");
1878
1879 LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1880 QString("%1 disconnected from RAOP events server.").arg(label));
1881}
1882
1884{
1885 QImage image = m_artwork.isEmpty() ? QImage() : QImage::fromData(m_artwork);
1886 auto duration = std::chrono::seconds(
1887 lroundf(static_cast<float>(m_progressEnd-m_progressStart) / m_frameRate));
1888 int position =
1890
1891 MythNotification *n = nullptr;
1892
1893 if (!update || !m_firstSend)
1894 {
1896 image, m_dmap, duration, position);
1897 }
1898 else
1899 {
1901 duration, position);
1902 }
1903 n->SetId(m_id);
1904 n->SetParent(this);
1905 n->SetDuration(5s);
1906 n->SetFullScreen(gCoreContext->GetBoolSetting("AirPlayFullScreen"));
1908 m_firstSend = true;
1909 delete n;
1910}
@ FORMAT_S16
@ AUDIOOUTPUT_MUSIC
Definition: audiosettings.h:24
static const std::array< const std::string, 8 > formats
bool isConfigured() const
Definition: audiooutput.h:80
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)
Definition: audiooutput.cpp:67
virtual std::chrono::milliseconds GetAudioBufferedTime(void)
report amount of audio buffered in milliseconds.
Definition: audiooutput.h:151
virtual std::chrono::milliseconds GetAudiotime(void)=0
virtual std::chrono::milliseconds LengthLastData(void) const
Definition: audiooutput.h:138
virtual int GetBytesPerFrame(void) const
Definition: audiooutput.h:86
static const int kMaxSizeBuffer
kMaxSizeBuffer is the maximum size of a buffer to be used with DecodeAudio
Definition: audiooutput.h:201
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 &notification)
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 kNew
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 ...
QList< int > m_audioFormat
static QString stringFromSeconds(int timeInSeconds)
stringFromSeconds:
ServerPool * m_eventServer
QHostAddress m_peerAddress
QMap< uint16_t, std::chrono::milliseconds > m_resends
void newEventClient(QTcpSocket *client)
void SendNotification(bool update=false)
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)
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
int ExpireAudio(std::chrono::milliseconds timestamp)
EVP_CIPHER_CTX * m_cctx
const EVP_CIPHER * m_cipher
const AVCodec * m_codec
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 &timestamp)
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
std::chrono::milliseconds m_cardLatency
static std::chrono::milliseconds NTPToLocal(uint32_t sec, uint32_t ticks)
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 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.
Definition: serverpool.h:60
void newDatagram(QByteArray, QHostAddress, quint16)
int tryListeningPort(int baseport, int range=1)
tryListeningPort
Definition: serverpool.cpp:730
int tryBindingPort(int baseport, int range=1)
tryBindingPort
Definition: serverpool.cpp:763
void close(void)
Definition: serverpool.cpp:374
void newConnection(QTcpSocket *)
qint64 writeDatagram(const char *data, qint64 size, const QHostAddress &addr, quint16 port)
Definition: serverpool.cpp:615
virtual void SetCurrentVolume(int value)
Definition: volumebase.cpp:123
virtual uint GetCurrentVolume(void) const
Definition: volumebase.cpp:118
static const std::array< const uint64_t, 4 > samples
Definition: element.cpp:46
unsigned short uint16_t
Definition: iso6937tables.h:3
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.
QString GetConfDir(void)
Definition: mythdirs.cpp:285
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
Definition: mythlogging.h:29
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MythNotificationCenter * GetNotificationCenter(void)
static constexpr std::chrono::milliseconds AUDIO_BUFFER
#define LOC
static constexpr uint8_t TIMING_REQUEST
#define EVP_PKEY_get_size
static constexpr uint8_t RANGE_RESEND
#define EVP_PKEY_get_id
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.
Definition: mythdate.cpp:15
MBASE_PUBLIC long long copy(QFile &dst, QFile &src, uint block_size=0)
Copies src file to dst file.
int FILE
Definition: mythburn.py:138
STL namespace.
QList< AudioData > * data