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