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