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 
17 #include "libmythbase/mythdirs.h"
18 #include "libmythbase/serverpool.h"
19 
22 
23 #include "mythraopdevice.h"
24 #include "mythraopconnection.h"
25 #include "mythairplayserver.h"
26 #include "libmythbase/mythchrono.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: ")
38 static constexpr size_t MAX_PACKET_SIZE { 2048 };
39 
40 EVP_PKEY *MythRAOPConnection::g_devPrivKey = nullptr;
42 
43 // RAOP RTP packet type
44 static constexpr uint8_t TIMING_REQUEST { 0x52 };
45 static constexpr uint8_t TIMING_RESPONSE { 0x53 };
46 static constexpr uint8_t SYNC { 0x54 };
47 static constexpr uint8_t FIRSTSYNC { 0x54 | 0x80 };
48 static constexpr uint8_t RANGE_RESEND { 0x55 };
49 static constexpr uint8_t AUDIO_RESEND { 0x56 };
50 static constexpr uint8_t AUDIO_DATA { 0x60 };
51 static constexpr uint8_t FIRSTAUDIO_DATA { 0x60 | 0x80 };
52 
53 // Size (in ms) of audio buffered in audio card
54 static 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
58 static constexpr std::chrono::milliseconds AUDIO_BUFFER { 100ms };
59 
60 class RaopNetStream : public QTextStream
61 {
62 public:
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 
76 MythRAOPConnection::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();
107  m_eventServer->close();
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
145  StopAudioTimer();
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();
166  delete m_clientTimingSocket;
167  m_clientTimingSocket = nullptr;
168  }
169 
170  // delete data socket
171  if (m_dataSocket)
172  {
173  m_dataSocket->disconnect();
174  m_dataSocket->close();
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
189  DestroyDecoder();
190 
191  // free decoded audio buffer
192  ResetAudio();
193 
194  // close audio device
196  // Tell listeners we're done
197  if (m_playbackStarted)
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();
220  if (!connect(m_dataSocket, &ServerPool::newDatagram,
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 
260 void 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:
304  ProcessTimeResponse(buf);
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  {
325  if (m_streamingStarted && seq != m_nextSequence)
326  SendResendRequest(timestamp, m_nextSequence, seq);
327 
328  m_nextSequence = seq + 1;
329  m_nextTimestamp = timestamp;
330  m_streamingStarted = true;
331  }
332 
333  if (!m_streamingStarted)
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 
371 void 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 
451 void 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 
492 void 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 
550 void MythRAOPConnection::ProcessTimeResponse(const QByteArray &buf)
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.
587 static constexpr uint64_t CLOCK_EPOCH {0x83aa7e80};
588 std::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 }
593 void 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 }
599 void 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 
615 bool 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;
717  int ret = AudioOutputUtil::DecodeAudio(ctx, samples,
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 {
743  if (!m_streamingStarted || !m_audio)
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  }
796  m_lastSequence++;
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 
840 int 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  {
884  CreateDecoder();
885  }
886 
887  if (m_audio && m_codec && m_codecContext)
888  {
889  StopAudioTimer();
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
913  if (!m_incomingPartial)
914  {
915  m_incomingHeaders.clear();
916  m_incomingContent.clear();
917  m_incomingSize = 0;
918 
919  QTextStream stream(data);
920  QString line;
921  do
922  {
923  line = stream.readLine();
924  if (line.size() == 0)
925  break;
926  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("Header(%1) = %2")
927  .arg(m_socket->peerAddress().toString(), line));
928  m_incomingHeaders.append(line);
929  if (line.contains("Content-Length:"))
930  {
931  m_incomingSize = line.mid(line.indexOf(" ") + 1).toInt();
932  }
933  }
934  while (!line.isNull());
935 
936  if (m_incomingHeaders.empty())
937  return;
938 
939  if (!stream.atEnd())
940  {
941  int pos = stream.pos();
942  if (pos > 0)
943  {
944  m_incomingContent.append(data.mid(pos));
945  }
946  }
947  }
948  else
949  {
950  m_incomingContent.append(data);
951  }
952 
953  // If we haven't received all the content yet, wait (see when receiving
954  // coverart
955  if (m_incomingContent.size() < m_incomingSize)
956  {
957  m_incomingPartial = true;
958  return;
959  }
960  m_incomingPartial = false;
961  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("Content(%1) = %2")
962  .arg(m_incomingContent.size()).arg(m_incomingContent.constData()));
963 
965 }
966 
967 void MythRAOPConnection::ProcessRequest(const QStringList &header,
968  const QByteArray &content)
969 {
970  if (header.isEmpty())
971  return;
972 
973  RawHash tags = FindTags(header);
974 
975  if (!tags.contains("CSeq"))
976  {
977  LOG(VB_PLAYBACK, LOG_ERR, LOC + "ProcessRequest: Didn't find CSeq");
978  return;
979  }
980 
981  QString option = header[0].left(header[0].indexOf(" "));
982 
983  // process RTP-info field
984  bool gotRTP = false;
985  uint16_t RTPseq = 0;
986  uint64_t RTPtimestamp = 0;
987  if (tags.contains("RTP-Info"))
988  {
989  gotRTP = true;
990  QString data = tags["RTP-Info"];
991  QStringList items = data.split(";");
992  for (const QString& item : std::as_const(items))
993  {
994  if (item.startsWith("seq"))
995  {
996  RTPseq = item.mid(item.indexOf("=") + 1).trimmed().toUShort();
997  }
998  else if (item.startsWith("rtptime"))
999  {
1000  RTPtimestamp = item.mid(item.indexOf("=") + 1).trimmed().toUInt();
1001  }
1002  }
1003  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("RTP-Info: seq=%1 rtptime=%2")
1004  .arg(RTPseq).arg(RTPtimestamp));
1005  }
1006 
1007  if (gCoreContext->GetBoolSetting("AirPlayPasswordEnabled", false))
1008  {
1009  if (m_nonce.isEmpty())
1010  {
1011  m_nonce = GenerateNonce();
1012  }
1013  if (!tags.contains("Authorization"))
1014  {
1015  // 60 seconds to enter password.
1016  m_watchdogTimer->start(1min);
1018  return;
1019  }
1020 
1021  QByteArray auth;
1022  if (DigestMd5Response(tags["Authorization"], option, m_nonce,
1023  gCoreContext->GetSetting("AirPlayPassword"),
1024  auth) == auth)
1025  {
1026  LOG(VB_PLAYBACK, LOG_INFO, LOC + "RAOP client authenticated");
1027  }
1028  else
1029  {
1030  LOG(VB_PLAYBACK, LOG_INFO, LOC + "RAOP authentication failed");
1032  return;
1033  }
1034  }
1035  *m_textStream << "RTSP/1.0 200 OK\r\n";
1036 
1037  if (tags.contains("Apple-Challenge"))
1038  {
1039  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("Received Apple-Challenge"));
1040 
1041  *m_textStream << "Apple-Response: ";
1042  if (!LoadKey())
1043  return;
1044  size_t tosize = EVP_PKEY_size(g_devPrivKey);
1045  std::vector<uint8_t>to;
1046  to.resize(tosize);
1047 
1048  QByteArray challenge =
1049  QByteArray::fromBase64(tags["Apple-Challenge"].toLatin1());
1050  int challenge_size = challenge.size();
1051  if (challenge_size != 16)
1052  {
1053  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1054  QString("Base64 decoded challenge size %1, expected 16")
1055  .arg(challenge_size));
1056  challenge_size = std::min(challenge_size, 16);
1057  }
1058 
1059  int i = 0;
1060  std::array<uint8_t,38> from {};
1061  std::copy(challenge.cbegin(), challenge.cbegin() + challenge_size,
1062  from.data());
1063  i += challenge_size;
1064  if (m_socket->localAddress().protocol() ==
1065  QAbstractSocket::IPv4Protocol)
1066  {
1067  uint32_t ip = m_socket->localAddress().toIPv4Address();
1068  ip = qToBigEndian(ip);
1069  memcpy(&from[i], &ip, 4);
1070  i += 4;
1071  }
1072  else if (m_socket->localAddress().protocol() ==
1073  QAbstractSocket::IPv6Protocol)
1074  {
1075  Q_IPV6ADDR ip = m_socket->localAddress().toIPv6Address();
1076  if(memcmp(&ip,
1077  "\x00\x00\x00\x00" "\x00\x00\x00\x00" "\x00\x00\xff\xff",
1078  12) == 0)
1079  {
1080  memcpy(&from[i], &ip[12], 4);
1081  i += 4;
1082  }
1083  else
1084  {
1085  memcpy(&from[i], &ip, 16);
1086  i += 16;
1087  }
1088  }
1089  if (m_hardwareId.size() == AIRPLAY_HARDWARE_ID_SIZE)
1090  {
1091  memcpy(&from[i], m_hardwareId.constData(), AIRPLAY_HARDWARE_ID_SIZE);
1093  }
1094  else
1095  {
1096  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1097  QString("Hardware MAC address size %1, expected %2")
1098  .arg(m_hardwareId.size()).arg(AIRPLAY_HARDWARE_ID_SIZE));
1099  }
1100 
1101  int pad = 32 - i;
1102  if (pad > 0)
1103  {
1104  memset(&from[i], 0, pad);
1105  i += pad;
1106  }
1107 
1108  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1109  QString("Full base64 response: '%1' size %2")
1110  .arg(QByteArray((char *)from.data(), i).toBase64().constData())
1111  .arg(i));
1112 
1113  auto *pctx = EVP_PKEY_CTX_new(g_devPrivKey, nullptr);
1114  if (nullptr == pctx)
1115  {
1116  LOG(VB_PLAYBACK, LOG_WARNING, LOC +
1117  QString("Cannot create ENV_PKEY_CTX from key. (%1)")
1118  .arg(ERR_error_string(ERR_get_error(), nullptr)));
1119  }
1120  else if (EVP_PKEY_sign_init(pctx) <= 0)
1121  {
1122  LOG(VB_PLAYBACK, LOG_WARNING, LOC +
1123  QString("EVP_PKEY_sign_init failed. (%1)")
1124  .arg(ERR_error_string(ERR_get_error(), nullptr)));
1125  }
1126  else if (EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PADDING) <= 0)
1127  {
1128  LOG(VB_PLAYBACK, LOG_WARNING, LOC +
1129  QString("Cannot set RSA_PKCS1_PADDING on context. (%1)")
1130  .arg(ERR_error_string(ERR_get_error(), nullptr)));
1131  }
1132  else if (EVP_PKEY_sign(pctx, to.data(), &tosize,
1133  from.data(), i) <= 0)
1134  {
1135  LOG(VB_PLAYBACK, LOG_WARNING, LOC +
1136  QString("EVP_PKEY_sign failed. (%1)")
1137  .arg(ERR_error_string(ERR_get_error(), nullptr)));
1138  }
1139 
1140  QByteArray base64 = QByteArray((const char *)to.data(), tosize).toBase64();
1141 
1142  for (int pos = base64.size() - 1; pos > 0; pos--)
1143  {
1144  if (base64[pos] == '=')
1145  base64[pos] = ' ';
1146  else
1147  break;
1148  }
1149  LOG(VB_PLAYBACK, LOG_DEBUG, QString("tSize=%1 tLen=%2 tResponse=%3")
1150  .arg(tosize).arg(base64.size()).arg(base64.constData()));
1151  *m_textStream << base64.trimmed() << "\r\n";
1152  }
1153 
1154  QString responseData;
1155  if (option == "OPTIONS")
1156  {
1157  *m_textStream << "Public: ANNOUNCE, SETUP, RECORD, PAUSE, FLUSH, "
1158  "TEARDOWN, OPTIONS, GET_PARAMETER, SET_PARAMETER, POST, GET\r\n";
1159  }
1160  else if (option == "ANNOUNCE")
1161  {
1162  QStringList lines = splitLines(content);
1163  for (const QString& line : std::as_const(lines))
1164  {
1165  if (line.startsWith("a=rsaaeskey:"))
1166  {
1167  QString key = line.mid(12).trimmed();
1168  QByteArray decodedkey = QByteArray::fromBase64(key.toLatin1());
1169  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1170  QString("RSAAESKey: %1 (base64 decoded size %2)")
1171  .arg(key).arg(decodedkey.size()));
1172 
1173  if (LoadKey())
1174  {
1175  size_t size = EVP_PKEY_get_size(g_devPrivKey);
1176  auto *pctx = EVP_PKEY_CTX_new(g_devPrivKey /* EVP_PKEY *pkey */,
1177  nullptr /* ENGINE *e */);
1178  m_sessionKey.resize(size);
1179  size_t size_out {size};
1180  if (nullptr == pctx)
1181  {
1182  LOG(VB_PLAYBACK, LOG_WARNING, LOC +
1183  QString("Cannot create ENV_PKEY_CTX from key. (%1)")
1184  .arg(ERR_error_string(ERR_get_error(), nullptr)));
1185  }
1186  else if (EVP_PKEY_decrypt_init(pctx) <= 0)
1187  {
1188  LOG(VB_PLAYBACK, LOG_WARNING, LOC + QString("EVP_PKEY_decrypt_init failed. (%1)")
1189  .arg(ERR_error_string(ERR_get_error(), nullptr)));
1190  }
1191  else if (EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_OAEP_PADDING) <= 0)
1192  {
1193  LOG(VB_PLAYBACK, LOG_WARNING, LOC +
1194  QString("Cannot set RSA_PKCS1_OAEP_PADDING on context. (%1)")
1195  .arg(ERR_error_string(ERR_get_error(), nullptr)));
1196  }
1197  else if (EVP_PKEY_decrypt(pctx, m_sessionKey.data(), &size_out,
1198  (const unsigned char *)decodedkey.constData(), decodedkey.size()) > 0)
1199  {
1200  // SUCCESS
1201  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1202  "Successfully decrypted AES key from RSA.");
1203  }
1204  else
1205  {
1206  LOG(VB_PLAYBACK, LOG_WARNING, LOC +
1207  QString("Failed to decrypt AES key from RSA. (%1)")
1208  .arg(ERR_error_string(ERR_get_error(), nullptr)));
1209  }
1210  }
1211  }
1212  else if (line.startsWith("a=aesiv:"))
1213  {
1214  QString aesiv = line.mid(8).trimmed();
1215  m_aesIV = QByteArray::fromBase64(aesiv.toLatin1());
1216  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1217  QString("AESIV: %1 (base64 decoded size %2)")
1218  .arg(aesiv).arg(m_aesIV.size()));
1219  }
1220  else if (line.startsWith("a=fmtp:"))
1221  {
1222  m_audioFormat.clear();
1223  QString format = line.mid(7).trimmed();
1224  QList<QString> formats = format.split(' ');
1225  for (const QString& fmt : std::as_const(formats))
1226  m_audioFormat.append(fmt.toInt());
1227 
1228  for (int fmt : std::as_const(m_audioFormat))
1229  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1230  QString("Audio parameter: %1").arg(fmt));
1234  m_frameRate = m_audioFormat[11];
1235  }
1236  }
1237  }
1238  else if (option == "SETUP")
1239  {
1240  if (tags.contains("Transport"))
1241  {
1242  // New client is trying to play audio, disconnect all the other clients
1243  auto *dev = qobject_cast<MythRAOPDevice*>(parent());
1244  if (dev != nullptr)
1245  dev->DeleteAllClients(this);
1246  gCoreContext->WantingPlayback(parent());
1247  m_playbackStarted = true;
1248 
1249  int control_port = 0;
1250  int timing_port = 0;
1251  QString data = tags["Transport"];
1252  QStringList items = data.split(";");
1253  bool events = false;
1254 
1255  for (const QString& item : std::as_const(items))
1256  {
1257  if (item.startsWith("control_port"))
1258  control_port = item.mid(item.indexOf("=") + 1).trimmed().toUInt();
1259  else if (item.startsWith("timing_port"))
1260  timing_port = item.mid(item.indexOf("=") + 1).trimmed().toUInt();
1261  else if (item.startsWith("events"))
1262  events = true;
1263  }
1264 
1265  LOG(VB_PLAYBACK, LOG_INFO, LOC +
1266  QString("Negotiated setup with client %1 on port %2")
1267  .arg(m_socket->peerAddress().toString())
1268  .arg(m_socket->peerPort()));
1269  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1270  QString("control port: %1 timing port: %2")
1271  .arg(control_port).arg(timing_port));
1272 
1273  m_peerAddress = m_socket->peerAddress();
1274 
1276  {
1277  m_clientControlSocket->disconnect();
1279  delete m_clientControlSocket;
1280  }
1281 
1282  m_clientControlSocket = new ServerPool(this);
1283  int controlbind_port =
1284  m_clientControlSocket->tryBindingPort(control_port,
1285  RAOP_PORT_RANGE);
1286  if (controlbind_port < 0)
1287  {
1288  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1289  QString("Failed to bind to client control port. "
1290  "Control of audio stream may fail"));
1291  }
1292  else
1293  {
1294  LOG(VB_PLAYBACK, LOG_INFO, LOC +
1295  QString("Bound to client control port %1 on port %2")
1296  .arg(control_port).arg(controlbind_port));
1297  }
1298  m_clientControlPort = control_port;
1299  connect(m_clientControlSocket,
1301  this,
1303 
1305  {
1306  m_clientTimingSocket->disconnect();
1308  delete m_clientTimingSocket;
1309  }
1310 
1311  m_clientTimingSocket = new ServerPool(this);
1312  int timingbind_port =
1313  m_clientTimingSocket->tryBindingPort(timing_port,
1314  RAOP_PORT_RANGE);
1315  if (timingbind_port < 0)
1316  {
1317  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1318  QString("Failed to bind to client timing port. "
1319  "Timing of audio stream will be incorrect"));
1320  }
1321  else
1322  {
1323  LOG(VB_PLAYBACK, LOG_INFO, LOC +
1324  QString("Bound to client timing port %1 on port %2")
1325  .arg(timing_port).arg(timingbind_port));
1326  }
1327  m_clientTimingPort = timing_port;
1328  connect(m_clientTimingSocket,
1330  this,
1332 
1333  if (m_eventServer)
1334  {
1335  // Should never get here, but just in case
1336  for (auto *client : std::as_const(m_eventClients))
1337  {
1338  client->disconnect();
1339  client->abort();
1340  delete client;
1341  }
1342  m_eventClients.clear();
1343  m_eventServer->disconnect();
1344  m_eventServer->close();
1345  delete m_eventServer;
1346  m_eventServer = nullptr;
1347  }
1348 
1349  if (events)
1350  {
1351  m_eventServer = new ServerPool(this);
1353  RAOP_PORT_RANGE);
1354  if (m_eventPort < 0)
1355  {
1356  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1357  "Failed to find a port for RAOP events server.");
1358  }
1359  else
1360  {
1361  LOG(VB_PLAYBACK, LOG_INFO, LOC +
1362  QString("Listening for RAOP events on port %1").arg(m_eventPort));
1365  }
1366  }
1367 
1368  if (OpenAudioDevice())
1369  {
1370  CreateDecoder();
1371  // Calculate audio card latency
1372  if (m_cardLatency < 0ms)
1373  {
1375  // if audio isn't started, start playing 500ms worth of silence
1376  // and measure timestamp difference
1377  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1378  QString("Audio hardware latency: %1ms")
1379  .arg((m_cardLatency + m_networkLatency).count()));
1380  }
1381  }
1382 
1383  // Recreate transport line with new ports value
1384  QString newdata;
1385  bool first = true;
1386  for (const QString& item : std::as_const(items))
1387  {
1388  if (!first)
1389  {
1390  newdata += ";";
1391  }
1392  if (item.startsWith("control_port"))
1393  {
1394  newdata += "control_port=" + QString::number(controlbind_port);
1395  }
1396  else if (item.startsWith("timing_port"))
1397  {
1398  newdata += "timing_port=" + QString::number(timingbind_port);
1399  }
1400  else if (item.startsWith("events"))
1401  {
1402  newdata += "event_port=" + QString::number(m_eventPort);
1403  }
1404  else
1405  {
1406  newdata += item;
1407  }
1408  first = false;
1409  }
1410  if (!first)
1411  {
1412  newdata += ";";
1413  }
1414  newdata += "server_port=" + QString::number(m_dataPort);
1415 
1416  *m_textStream << "Transport: " << newdata << "\r\n";
1417  *m_textStream << "Session: 1\r\n";
1418  *m_textStream << "Audio-Jack-Status: connected\r\n";
1419 
1420  // Ask for master clock value to determine time skew and average network latency
1421  SendTimeRequest();
1422  }
1423  else
1424  {
1425  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1426  "No Transport details found - Ignoring");
1427  }
1428  }
1429  else if (option == "RECORD")
1430  {
1431  if (gotRTP)
1432  {
1433  m_nextSequence = RTPseq;
1434  m_nextTimestamp = framesToMs(RTPtimestamp);
1435  }
1436 
1437  // Calculate audio card latency
1438  if (m_cardLatency > 0ms)
1439  {
1440  *m_textStream << QString("Audio-Latency: %1")
1441  .arg((m_cardLatency+m_networkLatency).count());
1442  }
1443  }
1444  else if (option == "FLUSH")
1445  {
1446  if (gotRTP)
1447  {
1448  m_nextSequence = RTPseq;
1449  m_nextTimestamp = framesToMs(RTPtimestamp);
1451  }
1452  // determine RTP timestamp of last sample played
1453  std::chrono::milliseconds timestamp = m_audioStarted && m_audio ?
1455  *m_textStream << "RTP-Info: rtptime=" << QString::number(MsToFrame(timestamp));
1456  m_streamingStarted = false;
1457  ResetAudio();
1458  }
1459  else if (option == "SET_PARAMETER")
1460  {
1461  if (tags.contains("Content-Type"))
1462  {
1463  if (tags["Content-Type"] == "text/parameters")
1464  {
1465  QString name = content.left(content.indexOf(":"));
1466  QString param = content.mid(content.indexOf(":") + 1).trimmed();
1467 
1468  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1469  QString("text/parameters: name=%1 parem=%2")
1470  .arg(name, param));
1471 
1472  if (name == "volume" && m_allowVolumeControl && m_audio)
1473  {
1474  float volume = (param.toFloat() + 30.0F) * 100.0F / 30.0F;
1475  if (volume < 0.01F)
1476  volume = 0.0F;
1477  LOG(VB_PLAYBACK, LOG_INFO,
1478  LOC + QString("Setting volume to %1 (raw %3)")
1479  .arg(volume).arg(param));
1480  m_audio->SetCurrentVolume((int)volume);
1481  }
1482  else if (name == "progress")
1483  {
1484  QStringList items = param.split("/");
1485  if (items.size() == 3)
1486  {
1487  m_progressStart = items[0].toUInt();
1488  m_progressCurrent = items[1].toUInt();
1489  m_progressEnd = items[2].toUInt();
1490  }
1491  int length =
1493  int current =
1495 
1496  LOG(VB_PLAYBACK, LOG_INFO,
1497  LOC +QString("Progress: %1/%2")
1498  .arg(stringFromSeconds(current),
1499  stringFromSeconds(length)));
1500  SendNotification(true);
1501  }
1502  }
1503  else if(tags["Content-Type"] == "image/none")
1504  {
1505  m_artwork.clear();
1506  SendNotification(false);
1507  }
1508  else if(tags["Content-Type"].startsWith("image/"))
1509  {
1510  // Receiving image coverart
1511  m_artwork = content;
1512  SendNotification(false);
1513  }
1514  else if (tags["Content-Type"] == "application/x-dmap-tagged")
1515  {
1516  // Receiving DMAP metadata
1518  LOG(VB_PLAYBACK, LOG_INFO,
1519  QString("Receiving Title:%1 Artist:%2 Album:%3 Format:%4")
1520  .arg(m_dmap["minm"], m_dmap["asar"],
1521  m_dmap["asal"], m_dmap["asfm"]));
1522  SendNotification(false);
1523  }
1524  }
1525  }
1526  else if (option == "GET_PARAMETER")
1527  {
1528  if (tags.contains("Content-Type"))
1529  {
1530  if (tags["Content-Type"] == "text/parameters")
1531  {
1532  QStringList lines = splitLines(content);
1533  *m_textStream << "Content-Type: text/parameters\r\n";
1534  for (const QString& line : std::as_const(lines))
1535  {
1536  if (line == "volume")
1537  {
1538  int volume = (m_allowVolumeControl && m_audio) ?
1539  m_audio->GetCurrentVolume() : 0;
1540  responseData += QString("volume: %1\r\n")
1541  .arg((volume * 30.0F / 100.0F) - 30.0F,1,'f',6,'0');
1542  }
1543  }
1544  }
1545  }
1546  }
1547  else if (option == "TEARDOWN")
1548  {
1549  m_socket->disconnectFromHost();
1550  return;
1551  }
1552  else
1553  {
1554  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("Command not handled: %1")
1555  .arg(option));
1556  }
1557  FinishResponse(m_textStream, m_socket, option, tags["CSeq"], responseData);
1558 }
1559 
1561  QTcpSocket *socket,
1562  QString &cseq)
1563 {
1564  if (!stream)
1565  return;
1566  *stream << "RTSP/1.0 401 Unauthorised\r\n";
1567  *stream << "Server: AirTunes/130.14\r\n";
1568  *stream << "WWW-Authenticate: Digest realm=\"raop\", ";
1569  *stream << "nonce=\"" + m_nonce + "\"\r\n";
1570  *stream << "CSeq: " << cseq << "\r\n";
1571  *stream << "\r\n";
1572  stream->flush();
1573  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1574  QString("Finished Authentication request %2, Send: %3")
1575  .arg(cseq).arg(socket->flush()));
1576 }
1577 
1578 void MythRAOPConnection::FinishResponse(RaopNetStream *stream, QTcpSocket *socket,
1579  QString &option, QString &cseq, QString &responseData)
1580 {
1581  if (!stream)
1582  return;
1583  *stream << "Server: AirTunes/130.14\r\n";
1584  *stream << "CSeq: " << cseq << "\r\n";
1585  if (!responseData.isEmpty())
1586  *stream << "Content-Length: " << QString::number(responseData.length()) << "\r\n\r\n" << responseData;
1587  else
1588  *stream << "\r\n";
1589  stream->flush();
1590  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("Finished %1 %2 , Send: %3")
1591  .arg(option, cseq, QString::number(socket->flush())));
1592 }
1593 
1600 {
1601  static QMutex s_lock;
1602  QMutexLocker locker(&s_lock);
1603 
1604  if (g_devPrivKey)
1605  return true;
1606 
1607  QString sName( "/RAOPKey.rsa" );
1608  FILE *file = fopen(GetConfDir().toUtf8() + sName.toUtf8(), "rb");
1609 
1610  if ( !file )
1611  {
1612  g_rsaLastError = tr("Failed to read key from: %1").arg(GetConfDir() + sName);
1613  LOG(VB_PLAYBACK, LOG_ERR, LOC + g_rsaLastError);
1614  return false;
1615  }
1616 
1617  g_devPrivKey = PEM_read_PrivateKey(file, nullptr, nullptr, nullptr);
1618  fclose(file);
1619 
1620  if (g_devPrivKey)
1621  {
1622  int id = EVP_PKEY_get_id(g_devPrivKey);
1623  int type = EVP_PKEY_type(id);
1624  if (type != EVP_PKEY_RSA)
1625  {
1626  g_rsaLastError = tr("Key is not a RSA private key.");
1627  EVP_PKEY_free(g_devPrivKey);
1628  g_devPrivKey = nullptr;
1629  LOG(VB_PLAYBACK, LOG_ERR, LOC + g_rsaLastError);
1630  return false;
1631  }
1632  g_rsaLastError = "";
1633  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "Loaded RSA private key");
1634  return true;
1635  }
1636 
1637  g_rsaLastError = tr("Failed to load RSA private key.");
1638  LOG(VB_PLAYBACK, LOG_ERR, LOC + g_rsaLastError);
1639  return false;
1640 }
1641 
1642 RawHash MythRAOPConnection::FindTags(const QStringList &lines)
1643 {
1644  RawHash result;
1645  if (lines.isEmpty())
1646  return result;
1647 
1648  for (const QString& line : std::as_const(lines))
1649  {
1650  int index = line.indexOf(":");
1651  if (index > 0)
1652  {
1653  result.insert(line.left(index).trimmed(),
1654  line.mid(index + 1).trimmed());
1655  }
1656  }
1657  return result;
1658 }
1659 
1660 QStringList MythRAOPConnection::splitLines(const QByteArray &lines)
1661 {
1662  QStringList list;
1663  QTextStream stream(lines);
1664 
1665  QString line;
1666  do
1667  {
1668  line = stream.readLine();
1669  if (!line.isNull())
1670  {
1671  list.append(line);
1672  }
1673  }
1674  while (!line.isNull());
1675 
1676  return list;
1677 }
1678 
1686 QString MythRAOPConnection::stringFromSeconds(int timeInSeconds)
1687 {
1688  QTime time = QTime(0,0).addSecs(timeInSeconds);
1689  return time.toString(time.hour() > 0 ? "HH:mm:ss" : "mm:ss");
1690 }
1691 
1697 std::chrono::milliseconds MythRAOPConnection::framesToMs(uint64_t frames) const
1698 {
1699  return std::chrono::milliseconds((frames * 1000ULL) / m_frameRate);
1700 }
1701 
1702 uint64_t MythRAOPConnection::MsToFrame(std::chrono::milliseconds millis) const
1703 {
1704  return millis.count() * m_frameRate / 1000ULL;
1705 }
1706 
1714 QMap<QString,QString> MythRAOPConnection::decodeDMAP(const QByteArray &dmap)
1715 {
1716  QMap<QString,QString> result;
1717  int offset = 8;
1718  while (offset < dmap.size())
1719  {
1720  QString tag = dmap.mid(offset, 4);
1721  offset += 4;
1722  uint32_t length = qFromBigEndian(*(uint32_t *)(dmap.constData() + offset));
1723  offset += sizeof(uint32_t);
1724  QString content = QString::fromUtf8(dmap.mid(offset,
1725  length).constData());
1726  offset += length;
1727  result.insert(tag, content);
1728  }
1729  return result;
1730 }
1731 
1733 {
1734  DestroyDecoder();
1735 
1736  // create an ALAC decoder
1737  m_codec = avcodec_find_decoder(AV_CODEC_ID_ALAC);
1738  if (!m_codec)
1739  {
1740  LOG(VB_PLAYBACK, LOG_ERR, LOC
1741  + "Failed to create ALAC decoder- going silent...");
1742  return false;
1743  }
1744 
1745  m_codecContext = avcodec_alloc_context3(m_codec);
1746  if (m_codecContext)
1747  {
1748  auto *extradata = new unsigned char[36];
1749  memset(extradata, 0, 36);
1750  if (m_audioFormat.size() < 12)
1751  {
1752  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1753  "Creating decoder but haven't seen audio format.");
1754  }
1755  else
1756  {
1757  uint32_t fs = m_audioFormat[1]; // frame size
1758  extradata[12] = (fs >> 24) & 0xff;
1759  extradata[13] = (fs >> 16) & 0xff;
1760  extradata[14] = (fs >> 8) & 0xff;
1761  extradata[15] = fs & 0xff;
1762  extradata[16] = m_channels; // channels
1763  extradata[17] = m_audioFormat[3]; // sample size
1764  extradata[18] = m_audioFormat[4]; // rice_historymult
1765  extradata[19] = m_audioFormat[5]; // rice_initialhistory
1766  extradata[20] = m_audioFormat[6]; // rice_kmodifier
1767  }
1768  m_codecContext->extradata = extradata;
1769  m_codecContext->extradata_size = 36;
1770  m_codecContext->ch_layout.nb_channels = m_channels;
1771  if (avcodec_open2(m_codecContext, m_codec, nullptr) < 0)
1772  {
1773  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1774  "Failed to open ALAC decoder - going silent...");
1775  DestroyDecoder();
1776  return false;
1777  }
1778  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "Opened ALAC decoder.");
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 {
1795  CloseAudioDevice();
1796 
1797  QString passthru = gCoreContext->GetBoolSetting("PassThruDeviceOverride", false)
1798  ? gCoreContext->GetSetting("PassThruOutputDevice") : QString();
1799  QString device = gCoreContext->GetSetting("AudioOutputDevice");
1800 
1801  m_audio = AudioOutput::OpenAudio(device, passthru, FORMAT_S16, m_channels,
1802  AV_CODEC_ID_NONE, m_frameRate, AUDIOOUTPUT_MUSIC,
1803  m_allowVolumeControl, false);
1804  if (!m_audio)
1805  {
1806  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1807  "Failed to open audio device. Going silent...");
1808  CloseAudioDevice();
1809  StartAudioTimer();
1810  return false;
1811  }
1812 
1813  QString error = m_audio->GetError();
1814  if (!error.isEmpty())
1815  {
1816  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1817  QString("Audio not initialised. Message was '%1'")
1818  .arg(error));
1819  CloseAudioDevice();
1820  StartAudioTimer();
1821  return false;
1822  }
1823 
1824  StopAudioTimer();
1825  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "Opened audio device.");
1826  return true;
1827 }
1828 
1830 {
1831  delete m_audio;
1832  m_audio = nullptr;
1833 }
1834 
1836 {
1837  if (m_audioTimer)
1838  return;
1839 
1840  m_audioTimer = new QTimer();
1842  m_audioTimer->start(5s);
1843 }
1844 
1846 {
1847  if (m_audioTimer)
1848  {
1849  m_audioTimer->stop();
1850  }
1851  delete m_audioTimer;
1852  m_audioTimer = nullptr;
1853 }
1854 
1859 std::chrono::milliseconds MythRAOPConnection::AudioCardLatency(void)
1860 {
1861  if (!m_audio)
1862  return 0ms;
1863 
1864  auto *samples = (int16_t *)av_mallocz(AudioOutput::kMaxSizeBuffer);
1865  int frames = AUDIOCARD_BUFFER.count() * m_frameRate / 1000;
1866  m_audio->AddData((char *)samples,
1867  frames * (m_sampleSize>>3) * m_channels,
1868  0ms,
1869  frames);
1870  av_free(samples);
1871  usleep(duration_cast<std::chrono::microseconds>(AUDIOCARD_BUFFER).count());
1872  std::chrono::milliseconds audiots = m_audio->GetAudiotime();
1873  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("AudioCardLatency: ts=%1ms")
1874  .arg(audiots.count()));
1875  return AUDIOCARD_BUFFER - audiots;
1876 }
1877 
1878 void MythRAOPConnection::newEventClient(QTcpSocket *client)
1879 {
1880  LOG(VB_PLAYBACK, LOG_INFO, LOC +
1881  QString("New connection from %1:%2 for RAOP events server.")
1882  .arg(client->peerAddress().toString()).arg(client->peerPort()));
1883 
1884  m_eventClients.append(client);
1885  connect(client, &QAbstractSocket::disconnected, this, &MythRAOPConnection::deleteEventClient);
1886 }
1887 
1889 {
1890  auto *client = qobject_cast<QTcpSocket *>(sender());
1891  QString label;
1892 
1893  if (client != nullptr)
1894  label = QString("%1:%2").arg(client->peerAddress().toString()).arg(client->peerPort());
1895  else
1896  label = QString("unknown");
1897 
1898  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1899  QString("%1 disconnected from RAOP events server.").arg(label));
1900 }
1901 
1903 {
1904  QImage image = m_artwork.isEmpty() ? QImage() : QImage::fromData(m_artwork);
1905  auto duration = std::chrono::seconds(
1906  lroundf(static_cast<float>(m_progressEnd-m_progressStart) / m_frameRate));
1907  int position =
1909 
1910  MythNotification *n = nullptr;
1911 
1912  if (!update || !m_firstSend)
1913  {
1915  image, m_dmap, duration, position);
1916  }
1917  else
1918  {
1920  duration, position);
1921  }
1922  n->SetId(m_id);
1923  n->SetParent(this);
1924  n->SetDuration(5s);
1925  n->SetFullScreen(gCoreContext->GetBoolSetting("AirPlayFullScreen"));
1927  m_firstSend = true;
1928  delete n;
1929 }
AudioOutput::GetError
QString GetError(void) const
Definition: audiooutput.h:147
AudioOutput::AddData
virtual bool AddData(void *buffer, int len, std::chrono::milliseconds timecode, int frames)=0
Add data to the audiobuffer for playback.
AudioPacket::data
QList< AudioData > * data
Definition: mythraopconnection.h:43
VolumeBase::SetCurrentVolume
virtual void SetCurrentVolume(int value)
Definition: volumebase.cpp:123
AudioOutput::kMaxSizeBuffer
static const int kMaxSizeBuffer
kMaxSizeBuffer is the maximum size of a buffer to be used with DecodeAudio
Definition: audiooutput.h:196
MythRAOPConnection::Init
bool Init(void)
Definition: mythraopconnection.cpp:203
MythRAOPConnection::StopAudioTimer
void StopAudioTimer(void)
Definition: mythraopconnection.cpp:1845
MythRAOPConnection::m_clientTimingPort
int m_clientTimingPort
Definition: mythraopconnection.h:133
DigestMd5Response
QByteArray DigestMd5Response(const QString &response, const QString &option, const QString &nonce, const QString &password, QByteArray &auth)
Definition: mythairplayserver.cpp:163
MythRAOPConnection::udpDataReady
void udpDataReady(QByteArray buf, const QHostAddress &peer, quint16 port)
Socket incoming data signal handler use for audio, control and timing socket.
Definition: mythraopconnection.cpp:260
build_compdb.dest
dest
Definition: build_compdb.py:9
hardwareprofile.smolt.timeout
float timeout
Definition: smolt.py:102
MythRAOPConnection::g_devPrivKey
static EVP_PKEY * g_devPrivKey
Definition: mythraopconnection.h:142
MythRAOPConnection::m_dataPort
int m_dataPort
Definition: mythraopconnection.h:129
RaopNetStream::RaopNetStream
RaopNetStream(QIODevice *device)
Definition: mythraopconnection.cpp:63
MythRAOPConnection::m_resends
QMap< uint16_t, std::chrono::milliseconds > m_resends
Definition: mythraopconnection.h:139
MythRAOPConnection::NTPToLocal
static std::chrono::milliseconds NTPToLocal(uint32_t sec, uint32_t ticks)
Definition: mythraopconnection.cpp:588
MythRAOPConnection::m_nextTimestamp
std::chrono::milliseconds m_nextTimestamp
Definition: mythraopconnection.h:174
MythRAOPConnection::m_dataSocket
ServerPool * m_dataSocket
Definition: mythraopconnection.h:128
error
static void error(const char *str,...)
Definition: vbi.cpp:37
MythRAOPConnection::FinishResponse
static void FinishResponse(RaopNetStream *stream, QTcpSocket *socket, QString &option, QString &cseq, QString &responseData)
Definition: mythraopconnection.cpp:1578
MythRAOPConnection::m_eventClients
QList< QTcpSocket * > m_eventClients
Definition: mythraopconnection.h:136
musicbrainzngs.musicbrainz.auth
def auth(u, p)
Definition: musicbrainz.py:292
MythRAOPConnection::m_incomingSize
int32_t m_incomingSize
Definition: mythraopconnection.h:126
ServerPool::newDatagram
void newDatagram(QByteArray, QHostAddress, quint16)
ServerPool::close
void close(void)
Definition: serverpool.cpp:374
VolumeBase::GetCurrentVolume
virtual uint GetCurrentVolume(void) const
Definition: volumebase.cpp:118
RaopNetStream::operator<<
RaopNetStream & operator<<(const QString &str)
Definition: mythraopconnection.cpp:66
audiooutpututil.h
FORMAT_S16
@ FORMAT_S16
Definition: audiooutputsettings.h:27
AudioOutput::Reset
virtual void Reset(void)=0
MythRAOPConnection::m_lastTimestamp
std::chrono::milliseconds m_lastTimestamp
Definition: mythraopconnection.h:171
MythRAOPConnection::ProcessSync
void ProcessSync(const QByteArray &buf)
Definition: mythraopconnection.cpp:371
MythNotification::SetDuration
void SetDuration(std::chrono::seconds Duration)
Contains a duration during which the notification will be displayed for. The duration is informative ...
Definition: mythnotification.cpp:144
FIRSTSYNC
static constexpr uint8_t FIRSTSYNC
Definition: mythraopconnection.cpp:47
MythCoreContext::emitTVPlaybackStopped
void emitTVPlaybackStopped(void)
Definition: mythcorecontext.h:291
ServerPool
Manages a collection of sockets listening on different ports.
Definition: serverpool.h:59
MythRAOPConnection::CloseAudioDevice
void CloseAudioDevice(void)
Definition: mythraopconnection.cpp:1829
MythRAOPConnection::CleanUp
void CleanUp(void)
Definition: mythraopconnection.cpp:142
ServerPool::tryBindingPort
int tryBindingPort(int baseport, int range=1)
tryBindingPort
Definition: serverpool.cpp:763
GenerateNonce
QString GenerateNonce(void)
Definition: mythairplayserver.cpp:146
TIMING_RESPONSE
static constexpr uint8_t TIMING_RESPONSE
Definition: mythraopconnection.cpp:45
build_compdb.content
content
Definition: build_compdb.py:38
RaopNetStream
Definition: mythraopconnection.cpp:60
AudioOutput::GetBytesPerFrame
virtual int GetBytesPerFrame(void) const
Definition: audiooutput.h:78
MythRAOPConnection::audioRetry
void audioRetry(void)
Definition: mythraopconnection.cpp:880
EVP_PKEY_get_id
#define EVP_PKEY_get_id
Definition: mythraopconnection.cpp:33
VERBOSE_LEVEL_CHECK
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
Definition: mythlogging.h:29
ServerPool::tryListeningPort
int tryListeningPort(int baseport, int range=1)
tryListeningPort
Definition: serverpool.cpp:730
MythRAOPConnection::AudioCardLatency
std::chrono::milliseconds AudioCardLatency(void)
AudioCardLatency: Description: Play silence and calculate audio latency between input / output.
Definition: mythraopconnection.cpp:1859
MythNotification
Definition: mythnotification.h:29
MythRAOPConnection::m_streamingStarted
bool m_streamingStarted
Definition: mythraopconnection.h:164
AudioData
Definition: mythraopconnection.h:33
MythRAOPConnection::m_channels
int m_channels
Definition: mythraopconnection.h:156
MythRAOPConnection::m_artwork
QByteArray m_artwork
Definition: mythraopconnection.h:195
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MythRAOPConnection::FinishAuthenticationResponse
void FinishAuthenticationResponse(RaopNetStream *stream, QTcpSocket *socket, QString &cseq)
Definition: mythraopconnection.cpp:1560
MythRAOPConnection::m_cardLatency
std::chrono::milliseconds m_cardLatency
Definition: mythraopconnection.h:177
MythRAOPConnection::~MythRAOPConnection
~MythRAOPConnection() override
Definition: mythraopconnection.cpp:93
MythRAOPConnection::MythRAOPConnection
MythRAOPConnection(QObject *parent, QTcpSocket *socket, QByteArray id, int port)
Definition: mythraopconnection.cpp:76
MythRAOPConnection::m_frameRate
int m_frameRate
Definition: mythraopconnection.h:158
build_compdb.file
file
Definition: build_compdb.py:55
RawHash
QHash< QString, QString > RawHash
Definition: mythraopconnection.h:31
AudioPacket
Definition: mythraopconnection.h:40
mythdirs.h
MythRAOPConnection::m_seqNum
uint16_t m_seqNum
Definition: mythraopconnection.h:168
MythRAOPConnection::m_clientTimingSocket
ServerPool * m_clientTimingSocket
Definition: mythraopconnection.h:132
AudioOutput::LengthLastData
virtual std::chrono::milliseconds LengthLastData(void) const
Definition: audiooutput.h:130
AudioPacket::seq
uint16_t seq
Definition: mythraopconnection.h:42
AUDIO_DATA
static constexpr uint8_t AUDIO_DATA
Definition: mythraopconnection.cpp:50
MythRAOPConnection::ExpireAudio
int ExpireAudio(std::chrono::milliseconds timestamp)
Definition: mythraopconnection.cpp:840
MythRAOPConnection::m_timeLastSync
std::chrono::milliseconds m_timeLastSync
Definition: mythraopconnection.h:176
AUDIO_RESEND
static constexpr uint8_t AUDIO_RESEND
Definition: mythraopconnection.cpp:49
MythDate::current
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:15
ServerPool::newConnection
void newConnection(QTcpSocket *)
mythburn.FILE
int FILE
Definition: mythburn.py:139
AudioOutput::OpenAudio
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:57
mythairplayserver.h
AudioOutput::GetAudioBufferedTime
virtual std::chrono::milliseconds GetAudioBufferedTime(void)
report amount of audio buffered in milliseconds.
Definition: audiooutput.h:143
MythRAOPConnection::m_audioTimer
QTimer * m_audioTimer
Definition: mythraopconnection.h:189
MythRAOPConnection::MsToFrame
uint64_t MsToFrame(std::chrono::milliseconds millis) const
Definition: mythraopconnection.cpp:1702
MythRAOPConnection::LoadKey
static bool LoadKey(void)
LoadKey.
Definition: mythraopconnection.cpp:1599
ServerPool::writeDatagram
qint64 writeDatagram(const char *data, qint64 size, const QHostAddress &addr, quint16 port)
Definition: serverpool.cpp:615
mythraopconnection.h
MythRAOPConnection::m_clientControlSocket
ServerPool * m_clientControlSocket
Definition: mythraopconnection.h:130
AIRPLAY_HARDWARE_ID_SIZE
static constexpr size_t AIRPLAY_HARDWARE_ID_SIZE
Definition: mythairplayserver.h:19
AudioOutputUtil::DecodeAudio
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...
Definition: audiooutpututil.cpp:232
mythlogging.h
GetConfDir
QString GetConfDir(void)
Definition: mythdirs.cpp:256
AudioOutput::Pause
virtual void Pause(bool paused)=0
MythRAOPConnection::m_networkLatency
std::chrono::milliseconds m_networkLatency
Definition: mythraopconnection.h:182
MythNotification::SetFullScreen
void SetFullScreen(bool FullScreen)
A notification may request to be displayed in full screen, this request may not be fullfilled should ...
Definition: mythnotification.cpp:115
MythFile::copy
MBASE_PUBLIC long long copy(QFile &dst, QFile &src, uint block_size=0)
Copies src file to dst file.
Definition: mythmiscutil.cpp:263
MythRAOPConnection::deleteEventClient
void deleteEventClient()
Definition: mythraopconnection.cpp:1888
MythRAOPConnection::m_cipher
const EVP_CIPHER * m_cipher
Definition: mythraopconnection.h:145
hardwareprofile.i18n.t
t
Definition: i18n.py:36
MythRAOPConnection::m_progressStart
uint32_t m_progressStart
Definition: mythraopconnection.h:192
MythRAOPConnection::m_hardwareId
QByteArray m_hardwareId
Definition: mythraopconnection.h:122
MythRAOPConnection::timeout
void timeout(void)
Definition: mythraopconnection.cpp:874
MythRAOPConnection::m_nonce
QString m_nonce
Definition: mythraopconnection.h:199
MythRAOPConnection::m_codec
const AVCodec * m_codec
Definition: mythraopconnection.h:153
MythRAOPConnection::ProcessRequest
void ProcessRequest(const QStringList &header, const QByteArray &content)
Definition: mythraopconnection.cpp:967
MythNotification::SetParent
void SetParent(void *Parent)
Contains the parent address. Required if id is set Id provided must match the parent address as provi...
Definition: mythnotification.cpp:106
MythRAOPConnection::m_dmap
DMAP m_dmap
Definition: mythraopconnection.h:196
MythRAOPConnection::m_dequeueAudioTimer
QTimer * m_dequeueAudioTimer
Definition: mythraopconnection.h:160
MythRAOPConnection::m_incomingContent
QByteArray m_incomingContent
Definition: mythraopconnection.h:124
MythNotification::kNew
static const Type kNew
Definition: mythnotification.h:32
formats
const std::array< const std::string, 8 > formats
Definition: vbilut.cpp:189
MythRAOPConnection::OpenAudioDevice
bool OpenAudioDevice(void)
Definition: mythraopconnection.cpp:1793
MythRAOPConnection::ExpireResendRequests
void ExpireResendRequests(std::chrono::milliseconds timestamp)
ExpireResendRequests: Expire resend requests that are older than timestamp.
Definition: mythraopconnection.cpp:492
MythRAOPConnection::m_codecContext
AVCodecContext * m_codecContext
Definition: mythraopconnection.h:154
AUDIOCARD_BUFFER
static constexpr std::chrono::milliseconds AUDIOCARD_BUFFER
Definition: mythraopconnection.cpp:54
MAX_PACKET_SIZE
static constexpr size_t MAX_PACKET_SIZE
Definition: mythraopconnection.cpp:38
MythRAOPConnection::m_clockSkew
std::chrono::milliseconds m_clockSkew
Definition: mythraopconnection.h:186
MythRAOPConnection::StartAudioTimer
void StartAudioTimer(void)
Definition: mythraopconnection.cpp:1835
MythRAOPConnection::ProcessTimeResponse
void ProcessTimeResponse(const QByteArray &buf)
ProcessTimeResponse: Calculate the network latency, we do not use the reference time send by itunes i...
Definition: mythraopconnection.cpp:550
FIRSTAUDIO_DATA
static constexpr uint8_t FIRSTAUDIO_DATA
Definition: mythraopconnection.cpp:51
MythRAOPConnection::m_incomingHeaders
QStringList m_incomingHeaders
Definition: mythraopconnection.h:123
MythRAOPConnection::m_currentTimestamp
std::chrono::milliseconds m_currentTimestamp
Definition: mythraopconnection.h:172
MythRAOPConnection::m_peerAddress
QHostAddress m_peerAddress
Definition: mythraopconnection.h:127
MythMediaNotification
Definition: mythnotification.h:180
MythRAOPConnection::m_progressCurrent
uint32_t m_progressCurrent
Definition: mythraopconnection.h:193
MythRAOPConnection::m_progressEnd
uint32_t m_progressEnd
Definition: mythraopconnection.h:194
MythRAOPConnection::m_incomingPartial
bool m_incomingPartial
Definition: mythraopconnection.h:125
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:55
MythRAOPConnection::m_bufferLength
std::chrono::milliseconds m_bufferLength
Definition: mythraopconnection.h:175
MythRAOPConnection::m_id
int m_id
Definition: mythraopconnection.h:202
AudioOutput::GetAudiotime
virtual std::chrono::milliseconds GetAudiotime(void)=0
MythRAOPConnection::m_audio
AudioOutput * m_audio
Definition: mythraopconnection.h:152
MythRAOPConnection::m_eventPort
int m_eventPort
Definition: mythraopconnection.h:135
MythRAOPConnection::m_aesIV
QByteArray m_aesIV
Definition: mythraopconnection.h:141
MythRAOPConnection::m_audioStarted
bool m_audioStarted
Definition: mythraopconnection.h:179
mythraopdevice.h
RAOP_PORT_RANGE
static constexpr int RAOP_PORT_RANGE
Definition: mythraopdevice.h:14
MythRAOPConnection::m_lastSequence
uint16_t m_lastSequence
Definition: mythraopconnection.h:170
MythRAOPConnection::SendResendRequest
void SendResendRequest(std::chrono::milliseconds timestamp, uint16_t expected, uint16_t got)
SendResendRequest: Request RAOP client to resend missed RTP packets.
Definition: mythraopconnection.cpp:451
MythCoreContext::GetBoolSetting
bool GetBoolSetting(const QString &key, bool defaultval=false)
Definition: mythcorecontext.cpp:910
MythRAOPConnection::m_sessionKey
std::vector< uint8_t > m_sessionKey
Definition: mythraopconnection.h:143
MythRAOPConnection::g_rsaLastError
static QString g_rsaLastError
Definition: mythraopconnection.h:150
MythRAOPConnection::m_watchdogTimer
QTimer * m_watchdogTimer
Definition: mythraopconnection.h:118
MythRAOPConnection::SendNotification
void SendNotification(bool update=false)
Definition: mythraopconnection.cpp:1902
MythRAOPConnection::m_socket
QTcpSocket * m_socket
Definition: mythraopconnection.h:120
MythRAOPConnection::m_sampleSize
int m_sampleSize
Definition: mythraopconnection.h:157
MythRAOPConnection::m_adjustedLatency
std::chrono::milliseconds m_adjustedLatency
Definition: mythraopconnection.h:178
mythcorecontext.h
TIMING_REQUEST
static constexpr uint8_t TIMING_REQUEST
Definition: mythraopconnection.cpp:44
MythRAOPConnection::m_eventServer
ServerPool * m_eventServer
Definition: mythraopconnection.h:134
MythRAOPConnection::m_clientControlPort
int m_clientControlPort
Definition: mythraopconnection.h:131
MythRAOPConnection::framesToMs
std::chrono::milliseconds framesToMs(uint64_t frames) const
framesDuration Description: return the duration in ms of frames
Definition: mythraopconnection.cpp:1697
RANGE_RESEND
static constexpr uint8_t RANGE_RESEND
Definition: mythraopconnection.cpp:48
MythRAOPConnection::timevalToNTP
static void timevalToNTP(timeval t, uint32_t &ntpSec, uint32_t &ntpTicks)
Definition: mythraopconnection.cpp:599
serverpool.h
audiooutput.h
CLOCK_EPOCH
static constexpr uint64_t CLOCK_EPOCH
Definition: mythraopconnection.cpp:587
MythRAOPConnection::decodeDMAP
static QMap< QString, QString > decodeDMAP(const QByteArray &dmap)
decodeDMAP:
Definition: mythraopconnection.cpp:1714
MythRAOPConnection::decodeAudioPacket
uint32_t decodeAudioPacket(uint8_t type, const QByteArray *buf, QList< AudioData > *dest)
Definition: mythraopconnection.cpp:650
AUDIO_BUFFER
static constexpr std::chrono::milliseconds AUDIO_BUFFER
Definition: mythraopconnection.cpp:58
GetNotificationCenter
MythNotificationCenter * GetNotificationCenter(void)
Definition: mythmainwindow.cpp:124
EVP_PKEY_get_size
#define EVP_PKEY_get_size
Definition: mythraopconnection.cpp:34
MythRAOPConnection::m_playbackStarted
bool m_playbackStarted
Definition: mythraopconnection.h:204
mythchrono.h
MythRAOPConnection::readClient
void readClient(void)
readClient: signal handler for RAOP client connection Handle initialisation of session
Definition: mythraopconnection.cpp:897
SYNC
static constexpr uint8_t SYNC
Definition: mythraopconnection.cpp:46
MythNotification::kUpdate
static const Type kUpdate
Definition: mythnotification.h:33
MythNotification::SetId
void SetId(int Id)
Contains the application registration id.
Definition: mythnotification.cpp:93
MythRAOPConnection::microsecondsToNTP
static void microsecondsToNTP(std::chrono::microseconds usec, uint32_t &ntpSec, uint32_t &ntpTicks)
Definition: mythraopconnection.cpp:593
MythCoreContext::WantingPlayback
void WantingPlayback(QObject *sender)
All the objects that have registered using MythCoreContext::RegisterForPlayback but sender will be ca...
Definition: mythcorecontext.cpp:1985
MythRAOPConnection::stringFromSeconds
static QString stringFromSeconds(int timeInSeconds)
stringFromSeconds:
Definition: mythraopconnection.cpp:1686
uint16_t
unsigned short uint16_t
Definition: iso6937tables.h:3
MythRAOPConnection::SendTimeRequest
void SendTimeRequest(void)
SendTimeRequest: Send a time request to the RAOP client.
Definition: mythraopconnection.cpp:514
AUDIOOUTPUT_MUSIC
@ AUDIOOUTPUT_MUSIC
Definition: audiosettings.h:24
MythRAOPConnection::ResetAudio
void ResetAudio(void)
Definition: mythraopconnection.cpp:863
MythRAOPConnection::m_audioQueue
QMap< std::chrono::milliseconds, AudioPacket > m_audioQueue
Definition: mythraopconnection.h:162
MythRAOPConnection::GetPacketType
static bool GetPacketType(const QByteArray &buf, uint8_t &type, uint16_t &seq, uint64_t &timestamp)
Definition: mythraopconnection.cpp:615
MythRAOPConnection::splitLines
static QStringList splitLines(const QByteArray &lines)
Definition: mythraopconnection.cpp:1660
MythRAOPConnection::m_cctx
EVP_CIPHER_CTX * m_cctx
Definition: mythraopconnection.h:149
MythRAOPConnection::newEventClient
void newEventClient(QTcpSocket *client)
Definition: mythraopconnection.cpp:1878
MythRAOPConnection::m_framesPerPacket
int m_framesPerPacket
Definition: mythraopconnection.h:159
MythRAOPConnection::DestroyDecoder
void DestroyDecoder(void)
Definition: mythraopconnection.cpp:1784
LOC
#define LOC
Definition: mythraopconnection.cpp:37
MythRAOPConnection::CreateDecoder
bool CreateDecoder(void)
Definition: mythraopconnection.cpp:1732
mythmainwindow.h
MythRAOPConnection::m_textStream
RaopNetStream * m_textStream
Definition: mythraopconnection.h:121
samples
static const std::array< const uint64_t, 4 > samples
Definition: element.cpp:46
MythRAOPConnection::m_nextSequence
uint16_t m_nextSequence
Definition: mythraopconnection.h:173
AudioOutput::IsPaused
virtual bool IsPaused(void) const =0
MythRAOPConnection::m_firstSend
bool m_firstSend
Definition: mythraopconnection.h:203
MythRAOPConnection::FindTags
static RawHash FindTags(const QStringList &lines)
Definition: mythraopconnection.cpp:1642
MythCoreContext::GetSetting
QString GetSetting(const QString &key, const QString &defaultval="")
Definition: mythcorecontext.cpp:902
MythPlaybackNotification
Definition: mythnotification.h:153
MythRAOPConnection::m_audioFormat
QList< int > m_audioFormat
Definition: mythraopconnection.h:155
MythRAOPConnection::m_allowVolumeControl
bool m_allowVolumeControl
Definition: mythraopconnection.h:165
MythRAOPConnection::ProcessAudio
void ProcessAudio(void)
Definition: mythraopconnection.cpp:741
MythNotificationCenter::Queue
bool Queue(const MythNotification &notification)
Queue a notification Queue() is thread-safe and can be called from anywhere.
Definition: mythnotificationcenter.cpp:1349