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