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