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