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