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