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