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 _NetStream : public QTextStream
48 {
49 public:
50  explicit _NetStream(QIODevice *device) : QTextStream(device)
51  {
52  };
53  _NetStream &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  foreach (QTcpSocket *client, 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;
239  uint16_t seq;
240  uint64_t timestamp;
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  LOG(VB_PLAYBACK, LOG_WARNING, LOC +
316  QString("Received unexpected resent packet %1")
317  .arg(seq));
318  }
319 
320  // Check that the audio packet is valid, do so by decoding it. If an error
321  // occurs, ask to resend it
322  auto *decoded = new QList<AudioData>();
323  int numframes = decodeAudioPacket(type, &buf, decoded);
324  if (numframes < 0)
325  {
326  // an error occurred, ask for the audio packet once again.
327  LOG(VB_PLAYBACK, LOG_ERR, LOC + QString("Error decoding audio"));
328  SendResendRequest(timestamp, seq, seq+1);
329  delete decoded;
330  return;
331  }
332  AudioPacket frames { seq, decoded };
333  m_audioQueue.insert(timestamp, frames);
334  ProcessAudio();
335 }
336 
337 void MythRAOPConnection::ProcessSync(const QByteArray &buf)
338 {
339  bool first = (uint8_t)buf[0] == 0x90; // First sync is 0x90,0x55
340  const char *req = buf.constData();
341  uint64_t current_ts = qFromBigEndian(*(uint32_t *)(req + 4));
342  uint64_t next_ts = qFromBigEndian(*(uint32_t *)(req + 16));
343 
344  uint64_t current = framesToMs(current_ts);
345  uint64_t next = framesToMs(next_ts);
346 
350 
351  if (current_ts > m_progressStart)
352  {
353  m_progressCurrent = next_ts;
354  SendNotification(true);
355  }
356 
357  if (first)
358  {
359  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("Receiving first SYNC packet"));
360  }
361  else
362  {
363  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("Receiving SYNC packet"));
364  }
365 
366  timeval t {};
367  gettimeofday(&t, nullptr);
368  m_timeLastSync = t.tv_sec * 1000 + t.tv_usec / 1000;
369 
370  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("SYNC: cur:%1 next:%2 time:%3")
372 
373  int64_t delay = framesToMs((uint64_t)m_audioQueue.size() * m_framesPerPacket);
374  int64_t audiots = m_audio->GetAudiotime();
375  int64_t currentLatency = 0LL;
376 
377  if (m_audioStarted)
378  {
379  currentLatency = audiots - (int64_t)m_currentTimestamp;
380  }
381 
382  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
383  QString("RAOP timestamps: about to play:%1 desired:%2 latency:%3")
384  .arg(audiots).arg(m_currentTimestamp)
385  .arg(currentLatency));
386 
387  delay += m_audio->GetAudioBufferedTime();
388  delay += currentLatency;
389 
390  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
391  QString("Queue=%1 buffer=%2ms ideal=%3ms diffts:%4ms")
392  .arg(m_audioQueue.size())
393  .arg(delay)
394  .arg(m_bufferLength)
395  .arg(m_bufferLength-delay));
396 
397  if (m_adjustedLatency <= 0 && m_audioStarted &&
398  (-currentLatency > AUDIOCARD_BUFFER))
399  {
400  // Too much delay in playback.
401  // The threshold is a value chosen to be loose enough so it doesn't
402  // trigger too often, but should be low enough to detect any accidental
403  // interruptions.
404  // Will drop some frames in next ProcessAudio
405  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
406  QString("Too much delay (%1ms), adjusting")
407  .arg(m_bufferLength - delay));
408 
410 
411  // Expire old audio
414  if (res > 0)
415  {
416  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("Drop %1 packets").arg(res));
417  }
418 
419  m_audioStarted = false;
420  }
421 }
422 
428  uint16_t expected, uint16_t got)
429 {
431  return;
432 
433  int16_t missed = (got < expected) ?
434  (int16_t)(((int32_t)got + UINT16_MAX + 1) - expected) :
435  got - expected;
436 
437  LOG(VB_PLAYBACK, LOG_INFO, LOC +
438  QString("Missed %1 packet(s): expected %2 got %3 ts:%4")
439  .arg(missed).arg(expected).arg(got).arg(timestamp));
440 
441  unsigned char req[8];
442  req[0] = 0x80;
443  req[1] = 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, sizeof(req),
450  == sizeof(req))
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  unsigned char req[32];
499  req[0] = 0x80;
500  req[1] = TIMING_REQUEST | 0x80;
501  // this is always 0x00 0x07 according to http://blog.technologeek.org/airtunes-v2
502  // no other value works
503  req[2] = 0x00;
504  req[3] = 0x07;
505  *(uint32_t *)(req + 4) = (uint32_t)0;
506  *(uint64_t *)(req + 8) = (uint64_t)0;
507  *(uint64_t *)(req + 16) = (uint64_t)0;
508  *(uint32_t *)(req + 24) = qToBigEndian((uint32_t)t.tv_sec);
509  *(uint32_t *)(req + 28) = qToBigEndian((uint32_t)t.tv_usec);
510 
511  if (m_clientTimingSocket->writeDatagram((char *)req, sizeof(req), m_peerAddress,
512  m_clientTimingPort) != sizeof(req))
513  {
514  LOG(VB_PLAYBACK, LOG_ERR, LOC + "Failed to send resend time request.");
515  return;
516  }
517  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
518  QString("Requesting master time (Local %1.%2)")
519  .arg(t.tv_sec).arg(t.tv_usec));
520 }
521 
528 void MythRAOPConnection::ProcessTimeResponse(const QByteArray &buf)
529 {
530  timeval t1 {};
531  timeval t2 {};
532  const char *req = buf.constData();
533 
534  t1.tv_sec = qFromBigEndian(*(uint32_t *)(req + 8));
535  t1.tv_usec = qFromBigEndian(*(uint32_t *)(req + 12));
536 
537  gettimeofday(&t2, nullptr);
538  uint64_t time1 = t1.tv_sec * 1000 + t1.tv_usec / 1000;
539  uint64_t time2 = t2.tv_sec * 1000 + t2.tv_usec / 1000;
540  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("Read back time (Local %1.%2)")
541  .arg(t1.tv_sec).arg(t1.tv_usec));
542  // network latency equal time difference in ms between request and response
543  // divide by two for approximate time of one way trip
544  m_networkLatency = (time2 - time1) / 2;
545  LOG(VB_AUDIO, LOG_DEBUG, LOC + QString("Network Latency: %1ms")
546  .arg(m_networkLatency));
547 
548  // now calculate the time difference between the client and us.
549  // this is NTP time, where sec is in seconds, and ticks is in 1/2^32s
550  uint32_t sec = qFromBigEndian(*(uint32_t *)(req + 24));
551  uint32_t ticks = qFromBigEndian(*(uint32_t *)(req + 28));
552  // convert ticks into ms
553  int64_t master = NTPToLocal(sec, ticks);
554  m_clockSkew = master - time2;
555 }
556 
557 uint64_t MythRAOPConnection::NTPToLocal(uint32_t sec, uint32_t ticks)
558 {
559  return (int64_t)sec * 1000LL + (((int64_t)ticks * 1000LL) >> 32);
560 }
561 
562 bool MythRAOPConnection::GetPacketType(const QByteArray &buf, uint8_t &type,
563  uint16_t &seq, uint64_t &timestamp)
564 {
565  // All RAOP packets start with | 0x80/0x90 (first sync) | PACKET_TYPE |
566  if ((uint8_t)buf[0] != 0x80 && (uint8_t)buf[0] != 0x90)
567  {
568  return false;
569  }
570 
571  type = buf[1];
572  // Is it first sync packet?
573  if ((uint8_t)buf[0] == 0x90 && type == FIRSTSYNC)
574  {
575  return true;
576  }
577  if (type != FIRSTAUDIO_DATA)
578  {
579  type &= ~0x80;
580  }
581 
583  return true;
584 
585  const char *ptr = buf.constData();
586  if (type == AUDIO_RESEND)
587  {
588  ptr += 4;
589  }
590  seq = qFromBigEndian(*(uint16_t *)(ptr + 2));
591  timestamp = qFromBigEndian(*(uint32_t *)(ptr + 4));
592  return true;
593 }
594 
595 // Audio decode / playback related routines
596 
598  const QByteArray *buf,
599  QList<AudioData> *dest)
600 {
601  const char *data_in = buf->constData();
602  int len = buf->size();
603  if (type == AUDIO_RESEND)
604  {
605  data_in += 4;
606  len -= 4;
607  }
608  data_in += 12;
609  len -= 12;
610  if (len < 16)
611  return -1;
612 
613  int aeslen = len & ~0xf;
614  unsigned char iv[16];
615  unsigned char decrypted_data[MAX_PACKET_SIZE];
616  memcpy(iv, m_AESIV.constData(), sizeof(iv));
617  AES_cbc_encrypt((const unsigned char *)data_in,
618  decrypted_data, aeslen,
619  &m_aesKey, iv, AES_DECRYPT);
620  memcpy(decrypted_data + aeslen, data_in + aeslen, len - aeslen);
621 
622  AVPacket tmp_pkt;
623  AVCodecContext *ctx = m_codeccontext;
624 
625  av_init_packet(&tmp_pkt);
626  tmp_pkt.data = decrypted_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;
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  QList<AudioData>::iterator it = frames.data->begin();
717  for (; it != frames.data->end(); ++it)
718  {
719  AudioData *data = &(*it);
720  int offset = 0;
721  int framecnt = 0;
722 
723  if (m_adjustedLatency > 0)
724  {
725  // calculate how many frames we have to drop to catch up
726  offset = (m_adjustedLatency * m_frameRate / 1000) *
728  if (offset > data->length)
729  offset = data->length;
730  framecnt = offset / m_audio->GetBytesPerFrame();
731  m_adjustedLatency -= framesToMs(framecnt+1);
732  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
733  QString("ProcessAudio: Dropping %1 frames to catch up "
734  "(%2ms to go)")
735  .arg(framecnt).arg(m_adjustedLatency));
736  timestamp += framesToMs(framecnt);
737  }
738  m_audio->AddData((char *)data->data + offset,
739  data->length - offset,
740  timestamp, framecnt);
741  timestamp += m_audio->LengthLastData();
742  }
743  i++;
744  m_audioStarted = true;
745  }
746  else // QMap is sorted, so no need to continue if not found
747  break;
748  }
749 
750  ExpireAudio(timestamp);
751  m_lastTimestamp = timestamp;
752 
753  // restart audio timer should we stop receiving data on regular interval,
754  // we need to continue processing the audio queue
756 }
757 
758 int MythRAOPConnection::ExpireAudio(uint64_t timestamp)
759 {
760  int res = 0;
761  QMutableMapIterator<uint64_t,AudioPacket> packet_it(m_audioQueue);
762  while (packet_it.hasNext())
763  {
764  packet_it.next();
765  if (packet_it.key() < timestamp)
766  {
767  AudioPacket frames = packet_it.value();
768  if (frames.data)
769  {
770  QList<AudioData>::iterator it = frames.data->begin();
771  for (; it != frames.data->end(); ++it)
772  {
773  av_free(it->data);
774  }
775  delete frames.data;
776  }
777  m_audioQueue.remove(packet_it.key());
778  res++;
779  }
780  }
781  return res;
782 }
783 
785 {
786  if (m_audio)
787  {
788  m_audio->Reset();
789  }
790  ExpireAudio(UINT64_MAX);
791  ExpireResendRequests(UINT64_MAX);
792  m_audioStarted = false;
793 }
794 
796 {
797  LOG(VB_PLAYBACK, LOG_INFO, LOC + "Closing connection after inactivity.");
798  m_socket->disconnectFromHost();
799 }
800 
802 {
803  if (!m_audio && OpenAudioDevice())
804  {
805  CreateDecoder();
806  }
807 
808  if (m_audio && m_codec && m_codeccontext)
809  {
810  StopAudioTimer();
811  }
812 }
813 
819 {
820  auto *socket = dynamic_cast<QTcpSocket *>(sender());
821  if (!socket)
822  return;
823 
824  QByteArray data = socket->readAll();
825  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("readClient(%1): ")
826  .arg(data.size()) + data.constData());
827 
828  // For big content, we may be called several times for a single packet
829  if (!m_incomingPartial)
830  {
831  m_incomingHeaders.clear();
832  m_incomingContent.clear();
833  m_incomingSize = 0;
834 
835  QTextStream stream(data);
836  QString line;
837  do
838  {
839  line = stream.readLine();
840  if (line.size() == 0)
841  break;
842  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("Header(%1) = %2")
843  .arg(m_socket->peerAddress().toString())
844  .arg(line));
845  m_incomingHeaders.append(line);
846  if (line.contains("Content-Length:"))
847  {
848  m_incomingSize = line.mid(line.indexOf(" ") + 1).toInt();
849  }
850  }
851  while (!line.isNull());
852 
853  if (m_incomingHeaders.empty())
854  return;
855 
856  if (!stream.atEnd())
857  {
858  int pos = stream.pos();
859  if (pos > 0)
860  {
861  m_incomingContent.append(data.mid(pos));
862  }
863  }
864  }
865  else
866  {
867  m_incomingContent.append(data);
868  }
869 
870  // If we haven't received all the content yet, wait (see when receiving
871  // coverart
872  if (m_incomingContent.size() < m_incomingSize)
873  {
874  m_incomingPartial = true;
875  return;
876  }
877  m_incomingPartial = false;
878  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("Content(%1) = %2")
879  .arg(m_incomingContent.size()).arg(m_incomingContent.constData()));
880 
882 }
883 
884 void MythRAOPConnection::ProcessRequest(const QStringList &header,
885  const QByteArray &content)
886 {
887  if (header.isEmpty())
888  return;
889 
890  RawHash tags = FindTags(header);
891 
892  if (!tags.contains("CSeq"))
893  {
894  LOG(VB_PLAYBACK, LOG_ERR, LOC + "ProcessRequest: Didn't find CSeq");
895  return;
896  }
897 
898  QString option = header[0].left(header[0].indexOf(" "));
899 
900  // process RTP-info field
901  bool gotRTP = false;
902  uint16_t RTPseq = 0;
903  uint64_t RTPtimestamp = 0;
904  if (tags.contains("RTP-Info"))
905  {
906  gotRTP = true;
907  QString data = tags["RTP-Info"];
908  QStringList items = data.split(";");
909  foreach (QString item, items)
910  {
911  if (item.startsWith("seq"))
912  {
913  RTPseq = item.mid(item.indexOf("=") + 1).trimmed().toUShort();
914  }
915  else if (item.startsWith("rtptime"))
916  {
917  RTPtimestamp = item.mid(item.indexOf("=") + 1).trimmed().toUInt();
918  }
919  }
920  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("RTP-Info: seq=%1 rtptime=%2")
921  .arg(RTPseq).arg(RTPtimestamp));
922  }
923 
924  if (gCoreContext->GetBoolSetting("AirPlayPasswordEnabled", false))
925  {
926  if (m_nonce.isEmpty())
927  {
929  }
930  if (!tags.contains("Authorization"))
931  {
932  // 60 seconds to enter password.
933  m_watchdogTimer->start(60000);
935  return;
936  }
937 
938  QByteArray auth;
939  if (DigestMd5Response(tags["Authorization"], option, m_nonce,
940  gCoreContext->GetSetting("AirPlayPassword"),
941  auth) == auth)
942  {
943  LOG(VB_PLAYBACK, LOG_INFO, LOC + "RAOP client authenticated");
944  }
945  else
946  {
947  LOG(VB_PLAYBACK, LOG_INFO, LOC + "RAOP authentication failed");
949  return;
950  }
951  }
952  *m_textStream << "RTSP/1.0 200 OK\r\n";
953 
954  if (tags.contains("Apple-Challenge"))
955  {
956  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("Received Apple-Challenge"));
957 
958  *m_textStream << "Apple-Response: ";
959  if (!LoadKey())
960  return;
961  int tosize = RSA_size(LoadKey());
962  auto *to = new uint8_t[tosize];
963 
964  QByteArray challenge =
965  QByteArray::fromBase64(tags["Apple-Challenge"].toLatin1());
966  int challenge_size = challenge.size();
967  if (challenge_size != 16)
968  {
969  LOG(VB_PLAYBACK, LOG_ERR, LOC +
970  QString("Decoded challenge size %1, expected 16")
971  .arg(challenge_size));
972  if (challenge_size > 16)
973  challenge_size = 16;
974  }
975 
976  int i = 0;
977  unsigned char from[38];
978  memcpy(from, challenge.constData(), challenge_size);
979  i += challenge_size;
980  if (m_socket->localAddress().protocol() ==
981  QAbstractSocket::IPv4Protocol)
982  {
983  uint32_t ip = m_socket->localAddress().toIPv4Address();
984  ip = qToBigEndian(ip);
985  memcpy(from + i, &ip, 4);
986  i += 4;
987  }
988  else if (m_socket->localAddress().protocol() ==
989  QAbstractSocket::IPv6Protocol)
990  {
991  Q_IPV6ADDR ip = m_socket->localAddress().toIPv6Address();
992  if(memcmp(&ip,
993  "\x00\x00\x00\x00" "\x00\x00\x00\x00" "\x00\x00\xff\xff",
994  12) == 0)
995  {
996  memcpy(from + i, &ip[12], 4);
997  i += 4;
998  }
999  else
1000  {
1001  memcpy(from + i, &ip, 16);
1002  i += 16;
1003  }
1004  }
1005  memcpy(from + i, m_hardwareId.constData(), AIRPLAY_HARDWARE_ID_SIZE);
1007 
1008  int pad = 32 - i;
1009  if (pad > 0)
1010  {
1011  memset(from + i, 0, pad);
1012  i += pad;
1013  }
1014 
1015  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1016  QString("Full base64 response: '%1' size %2")
1017  .arg(QByteArray((const char *)from, i).toBase64().constData())
1018  .arg(i));
1019 
1020  RSA_private_encrypt(i, from, to, LoadKey(), RSA_PKCS1_PADDING);
1021 
1022  QByteArray base64 = QByteArray((const char *)to, tosize).toBase64();
1023  delete[] to;
1024 
1025  for (int pos = base64.size() - 1; pos > 0; pos--)
1026  {
1027  if (base64[pos] == '=')
1028  base64[pos] = ' ';
1029  else
1030  break;
1031  }
1032  LOG(VB_PLAYBACK, LOG_DEBUG, QString("tSize=%1 tLen=%2 tResponse=%3")
1033  .arg(tosize).arg(base64.size()).arg(base64.constData()));
1034  *m_textStream << base64.trimmed() << "\r\n";
1035  }
1036 
1037  QString responseData;
1038  if (option == "OPTIONS")
1039  {
1040  *m_textStream << "Public: ANNOUNCE, SETUP, RECORD, PAUSE, FLUSH, "
1041  "TEARDOWN, OPTIONS, GET_PARAMETER, SET_PARAMETER, POST, GET\r\n";
1042  }
1043  else if (option == "ANNOUNCE")
1044  {
1045  QStringList lines = splitLines(content);
1046  foreach (QString line, lines)
1047  {
1048  if (line.startsWith("a=rsaaeskey:"))
1049  {
1050  QString key = line.mid(12).trimmed();
1051  QByteArray decodedkey = QByteArray::fromBase64(key.toLatin1());
1052  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1053  QString("RSAAESKey: %1 (decoded size %2)")
1054  .arg(key).arg(decodedkey.size()));
1055 
1056  if (LoadKey())
1057  {
1058  int size = sizeof(char) * RSA_size(LoadKey());
1059  char *decryptedkey = new char[size];
1060  if (RSA_private_decrypt(decodedkey.size(),
1061  (const unsigned char *)decodedkey.constData(),
1062  (unsigned char *)decryptedkey,
1063  LoadKey(), RSA_PKCS1_OAEP_PADDING))
1064  {
1065  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1066  "Successfully decrypted AES key from RSA.");
1067  AES_set_decrypt_key((const unsigned char *)decryptedkey,
1068  128, &m_aesKey);
1069  }
1070  else
1071  {
1072  LOG(VB_PLAYBACK, LOG_WARNING, LOC +
1073  "Failed to decrypt AES key from RSA.");
1074  }
1075  delete [] decryptedkey;
1076  }
1077  }
1078  else if (line.startsWith("a=aesiv:"))
1079  {
1080  QString aesiv = line.mid(8).trimmed();
1081  m_AESIV = QByteArray::fromBase64(aesiv.toLatin1());
1082  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1083  QString("AESIV: %1 (decoded size %2)")
1084  .arg(aesiv).arg(m_AESIV.size()));
1085  }
1086  else if (line.startsWith("a=fmtp:"))
1087  {
1088  m_audioFormat.clear();
1089  QString format = line.mid(7).trimmed();
1090  QList<QString> formats = format.split(' ');
1091  foreach (QString fmt, formats)
1092  m_audioFormat.append(fmt.toInt());
1093 
1094  foreach (int fmt, m_audioFormat)
1095  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1096  QString("Audio parameter: %1").arg(fmt));
1100  m_frameRate = m_audioFormat[11];
1101  }
1102  }
1103  }
1104  else if (option == "SETUP")
1105  {
1106  if (tags.contains("Transport"))
1107  {
1108  // New client is trying to play audio, disconnect all the other clients
1109  auto dev = dynamic_cast<MythRAOPDevice*>(parent());
1110  if (dev != nullptr)
1111  dev->DeleteAllClients(this);
1112  gCoreContext->WantingPlayback(parent());
1113  m_playbackStarted = true;
1114 
1115  int control_port = 0;
1116  int timing_port = 0;
1117  QString data = tags["Transport"];
1118  QStringList items = data.split(";");
1119  bool events = false;
1120 
1121  foreach (QString item, items)
1122  {
1123  if (item.startsWith("control_port"))
1124  control_port = item.mid(item.indexOf("=") + 1).trimmed().toUInt();
1125  else if (item.startsWith("timing_port"))
1126  timing_port = item.mid(item.indexOf("=") + 1).trimmed().toUInt();
1127  else if (item.startsWith("events"))
1128  events = true;
1129  }
1130 
1131  LOG(VB_PLAYBACK, LOG_INFO, LOC +
1132  QString("Negotiated setup with client %1 on port %2")
1133  .arg(m_socket->peerAddress().toString())
1134  .arg(m_socket->peerPort()));
1135  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1136  QString("control port: %1 timing port: %2")
1137  .arg(control_port).arg(timing_port));
1138 
1139  m_peerAddress = m_socket->peerAddress();
1140 
1142  {
1143  m_clientControlSocket->disconnect();
1145  delete m_clientControlSocket;
1146  }
1147 
1148  m_clientControlSocket = new ServerPool(this);
1149  int controlbind_port =
1150  m_clientControlSocket->tryBindingPort(control_port,
1151  RAOP_PORT_RANGE);
1152  if (controlbind_port < 0)
1153  {
1154  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1155  QString("Failed to bind to client control port. "
1156  "Control of audio stream may fail"));
1157  }
1158  else
1159  {
1160  LOG(VB_PLAYBACK, LOG_INFO, LOC +
1161  QString("Bound to client control port %1 on port %2")
1162  .arg(control_port).arg(controlbind_port));
1163  }
1164  m_clientControlPort = control_port;
1165  connect(m_clientControlSocket,
1166  SIGNAL(newDatagram(QByteArray, QHostAddress, quint16)),
1167  this,
1168  SLOT(udpDataReady(QByteArray, QHostAddress, quint16)));
1169 
1171  {
1172  m_clientTimingSocket->disconnect();
1174  delete m_clientTimingSocket;
1175  }
1176 
1177  m_clientTimingSocket = new ServerPool(this);
1178  int timingbind_port =
1179  m_clientTimingSocket->tryBindingPort(timing_port,
1180  RAOP_PORT_RANGE);
1181  if (timingbind_port < 0)
1182  {
1183  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1184  QString("Failed to bind to client timing port. "
1185  "Timing of audio stream will be incorrect"));
1186  }
1187  else
1188  {
1189  LOG(VB_PLAYBACK, LOG_INFO, LOC +
1190  QString("Bound to client timing port %1 on port %2")
1191  .arg(timing_port).arg(timingbind_port));
1192  }
1193  m_clientTimingPort = timing_port;
1194  connect(m_clientTimingSocket,
1195  SIGNAL(newDatagram(QByteArray, QHostAddress, quint16)),
1196  this,
1197  SLOT(udpDataReady(QByteArray, QHostAddress, quint16)));
1198 
1199  if (m_eventServer)
1200  {
1201  // Should never get here, but just in case
1202  QTcpSocket *client;
1203  foreach (client, m_eventClients)
1204  {
1205  client->disconnect();
1206  client->abort();
1207  delete client;
1208  }
1209  m_eventClients.clear();
1210  m_eventServer->disconnect();
1211  m_eventServer->close();
1212  delete m_eventServer;
1213  m_eventServer = nullptr;
1214  }
1215 
1216  if (events)
1217  {
1218  m_eventServer = new ServerPool(this);
1220  RAOP_PORT_RANGE);
1221  if (m_eventPort < 0)
1222  {
1223  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1224  "Failed to find a port for RAOP events server.");
1225  }
1226  else
1227  {
1228  LOG(VB_PLAYBACK, LOG_INFO, LOC +
1229  QString("Listening for RAOP events on port %1").arg(m_eventPort));
1230  connect(m_eventServer, SIGNAL(newConnection(QTcpSocket *)),
1231  this, SLOT(newEventClient(QTcpSocket *)));
1232  }
1233  }
1234 
1235  if (OpenAudioDevice())
1236  {
1237  CreateDecoder();
1238  // Calculate audio card latency
1239  if (m_cardLatency < 0)
1240  {
1242  // if audio isn't started, start playing 500ms worth of silence
1243  // and measure timestamp difference
1244  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1245  QString("Audio hardware latency: %1ms")
1247  }
1248  }
1249 
1250  // Recreate transport line with new ports value
1251  QString newdata;
1252  bool first = true;
1253  foreach (QString item, items)
1254  {
1255  if (!first)
1256  {
1257  newdata += ";";
1258  }
1259  if (item.startsWith("control_port"))
1260  {
1261  newdata += "control_port=" + QString::number(controlbind_port);
1262  }
1263  else if (item.startsWith("timing_port"))
1264  {
1265  newdata += "timing_port=" + QString::number(timingbind_port);
1266  }
1267  else if (item.startsWith("events"))
1268  {
1269  newdata += "event_port=" + QString::number(m_eventPort);
1270  }
1271  else
1272  {
1273  newdata += item;
1274  }
1275  first = false;
1276  }
1277  if (!first)
1278  {
1279  newdata += ";";
1280  }
1281  newdata += "server_port=" + QString::number(m_dataPort);
1282 
1283  *m_textStream << "Transport: " << newdata << "\r\n";
1284  *m_textStream << "Session: 1\r\n";
1285  *m_textStream << "Audio-Jack-Status: connected\r\n";
1286 
1287  // Ask for master clock value to determine time skew and average network latency
1288  SendTimeRequest();
1289  }
1290  else
1291  {
1292  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1293  "No Transport details found - Ignoring");
1294  }
1295  }
1296  else if (option == "RECORD")
1297  {
1298  if (gotRTP)
1299  {
1300  m_nextSequence = RTPseq;
1301  m_nextTimestamp = RTPtimestamp;
1302  }
1303 
1304  // Calculate audio card latency
1305  if (m_cardLatency > 0)
1306  {
1307  *m_textStream << QString("Audio-Latency: %1")
1309  }
1310  }
1311  else if (option == "FLUSH")
1312  {
1313  if (gotRTP)
1314  {
1315  m_nextSequence = RTPseq;
1316  m_nextTimestamp = framesToMs(RTPtimestamp);
1318  }
1319  // determine RTP timestamp of last sample played
1320  uint64_t timestamp = m_audioStarted && m_audio ?
1322  *m_textStream << "RTP-Info: rtptime=" << QString::number(timestamp);
1323  m_streamingStarted = false;
1324  ResetAudio();
1325  }
1326  else if (option == "SET_PARAMETER")
1327  {
1328  if (tags.contains("Content-Type"))
1329  {
1330  if (tags["Content-Type"] == "text/parameters")
1331  {
1332  QString name = content.left(content.indexOf(":"));
1333  QString param = content.mid(content.indexOf(":") + 1).trimmed();
1334 
1335  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1336  QString("text/parameters: name=%1 parem=%2")
1337  .arg(name).arg(param));
1338 
1339  if (name == "volume" && m_allowVolumeControl && m_audio)
1340  {
1341  float volume = (param.toFloat() + 30.0F) * 100.0F / 30.0F;
1342  if (volume < 0.01F)
1343  volume = 0.0F;
1344  LOG(VB_PLAYBACK, LOG_INFO,
1345  LOC + QString("Setting volume to %1 (raw %3)")
1346  .arg(volume).arg(param));
1347  m_audio->SetCurrentVolume((int)volume);
1348  }
1349  else if (name == "progress")
1350  {
1351  QStringList items = param.split("/");
1352  if (items.size() == 3)
1353  {
1354  m_progressStart = items[0].toUInt();
1355  m_progressCurrent = items[1].toUInt();
1356  m_progressEnd = items[2].toUInt();
1357  }
1358  int length =
1360  int current =
1362 
1363  LOG(VB_PLAYBACK, LOG_INFO,
1364  LOC +QString("Progress: %1/%2")
1365  .arg(stringFromSeconds(current))
1366  .arg(stringFromSeconds(length)));
1367  SendNotification(true);
1368  }
1369  }
1370  else if(tags["Content-Type"] == "image/none")
1371  {
1372  m_artwork.clear();
1373  SendNotification(false);
1374  }
1375  else if(tags["Content-Type"].startsWith("image/"))
1376  {
1377  // Receiving image coverart
1378  m_artwork = content;
1379  SendNotification(false);
1380  }
1381  else if (tags["Content-Type"] == "application/x-dmap-tagged")
1382  {
1383  // Receiving DMAP metadata
1385  LOG(VB_PLAYBACK, LOG_INFO,
1386  QString("Receiving Title:%1 Artist:%2 Album:%3 Format:%4")
1387  .arg(m_dmap["minm"]).arg(m_dmap["asar"])
1388  .arg(m_dmap["asal"]).arg(m_dmap["asfm"]));
1389  SendNotification(false);
1390  }
1391  }
1392  }
1393  else if (option == "GET_PARAMETER")
1394  {
1395  if (tags.contains("Content-Type"))
1396  {
1397  if (tags["Content-Type"] == "text/parameters")
1398  {
1399  QStringList lines = splitLines(content);
1400  *m_textStream << "Content-Type: text/parameters\r\n";
1401  foreach (QString line, lines)
1402  {
1403  if (line == "volume")
1404  {
1405  int volume = (m_allowVolumeControl && m_audio) ?
1406  m_audio->GetCurrentVolume() : 0;
1407  responseData += QString("volume: %1\r\n")
1408  .arg(volume * 30.0F / 100.0F - 30.0F,1,'f',6,'0');
1409  }
1410  }
1411  }
1412  }
1413  }
1414  else if (option == "TEARDOWN")
1415  {
1416  m_socket->disconnectFromHost();
1417  return;
1418  }
1419  else
1420  {
1421  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("Command not handled: %1")
1422  .arg(option));
1423  }
1424  FinishResponse(m_textStream, m_socket, option, tags["CSeq"], responseData);
1425 }
1426 
1428  QTcpSocket *socket,
1429  QString &cseq)
1430 {
1431  if (!stream)
1432  return;
1433  *stream << "RTSP/1.0 401 Unauthorised\r\n";
1434  *stream << "Server: AirTunes/130.14\r\n";
1435  *stream << "WWW-Authenticate: Digest realm=\"raop\", ";
1436  *stream << "nonce=\"" + m_nonce + "\"\r\n";
1437  *stream << "CSeq: " << cseq << "\r\n";
1438  *stream << "\r\n";
1439  stream->flush();
1440  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1441  QString("Finished Authentication request %2, Send: %3")
1442  .arg(cseq).arg(socket->flush()));
1443 }
1444 
1445 void MythRAOPConnection::FinishResponse(_NetStream *stream, QTcpSocket *socket,
1446  QString &option, QString &cseq, QString &responseData)
1447 {
1448  if (!stream)
1449  return;
1450  *stream << "Server: AirTunes/130.14\r\n";
1451  *stream << "CSeq: " << cseq << "\r\n";
1452  if (responseData.length())
1453  *stream << "Content-Length: " << QString::number(responseData.length()) << "\r\n\r\n" << responseData;
1454  else
1455  *stream << "\r\n";
1456  stream->flush();
1457  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("Finished %1 %2 , Send: %3")
1458  .arg(option).arg(cseq).arg(socket->flush()));
1459 }
1460 
1467 {
1468  static QMutex s_lock;
1469  QMutexLocker locker(&s_lock);
1470 
1471  if (g_rsa)
1472  return g_rsa;
1473 
1474  QString sName( "/RAOPKey.rsa" );
1475  FILE *file = fopen(GetConfDir().toUtf8() + sName.toUtf8(), "rb");
1476 
1477  if ( !file )
1478  {
1479  g_rsaLastError = tr("Failed to read key from: %1").arg(GetConfDir() + sName);
1480  g_rsa = nullptr;
1481  LOG(VB_PLAYBACK, LOG_ERR, LOC + g_rsaLastError);
1482  return nullptr;
1483  }
1484 
1485  g_rsa = PEM_read_RSAPrivateKey(file, nullptr, nullptr, nullptr);
1486  fclose(file);
1487 
1488  if (g_rsa)
1489  {
1490  g_rsaLastError = "";
1491  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1492  QString("Loaded RSA private key (%1)").arg(RSA_check_key(g_rsa)));
1493  return g_rsa;
1494  }
1495 
1496  g_rsaLastError = tr("Failed to load RSA private key.");
1497  g_rsa = nullptr;
1498  LOG(VB_PLAYBACK, LOG_ERR, LOC + g_rsaLastError);
1499  return nullptr;
1500 }
1501 
1502 RawHash MythRAOPConnection::FindTags(const QStringList &lines)
1503 {
1504  RawHash result;
1505  if (lines.isEmpty())
1506  return result;
1507 
1508  for (int i = 0; i < lines.size(); i++)
1509  {
1510  int index = lines[i].indexOf(":");
1511  if (index > 0)
1512  {
1513  result.insert(lines[i].left(index).trimmed(),
1514  lines[i].mid(index + 1).trimmed());
1515  }
1516  }
1517  return result;
1518 }
1519 
1520 QStringList MythRAOPConnection::splitLines(const QByteArray &lines)
1521 {
1522  QStringList list;
1523  QTextStream stream(lines);
1524 
1525  QString line;
1526  do
1527  {
1528  line = stream.readLine();
1529  if (!line.isNull())
1530  {
1531  list.append(line);
1532  }
1533  }
1534  while (!line.isNull());
1535 
1536  return list;
1537 }
1538 
1546 QString MythRAOPConnection::stringFromSeconds(int timeInSeconds)
1547 {
1548  int hour = timeInSeconds / 3600;
1549  int minute = (timeInSeconds - hour * 3600) / 60;
1550  int seconds = timeInSeconds - hour * 3600 - minute * 60;
1551  QString str;
1552 
1553  if (hour)
1554  {
1555  str += QString("%1:").arg(hour);
1556  }
1557  if (minute < 10)
1558  {
1559  str += "0";
1560  }
1561  str += QString("%1:").arg(minute);
1562  if (seconds < 10)
1563  {
1564  str += "0";
1565  }
1566  str += QString::number(seconds);
1567  return str;
1568 }
1569 
1576 {
1577  return (frames * 1000ULL) / m_frameRate;
1578 }
1579 
1587 QMap<QString,QString> MythRAOPConnection::decodeDMAP(const QByteArray &dmap)
1588 {
1589  QMap<QString,QString> result;
1590  int offset = 8;
1591  while (offset < dmap.size())
1592  {
1593  QString tag = dmap.mid(offset, 4);
1594  offset += 4;
1595  uint32_t length = qFromBigEndian(*(uint32_t *)(dmap.constData() + offset));
1596  offset += sizeof(uint32_t);
1597  QString content = QString::fromUtf8(dmap.mid(offset,
1598  length).constData());
1599  offset += length;
1600  result.insert(tag, content);
1601  }
1602  return result;
1603 }
1604 
1606 {
1607  DestroyDecoder();
1608 
1609  // create an ALAC decoder
1610  m_codec = avcodec_find_decoder(AV_CODEC_ID_ALAC);
1611  if (!m_codec)
1612  {
1613  LOG(VB_PLAYBACK, LOG_ERR, LOC
1614  + "Failed to create ALAC decoder- going silent...");
1615  return false;
1616  }
1617 
1618  m_codeccontext = avcodec_alloc_context3(m_codec);
1619  if (m_codeccontext)
1620  {
1621  auto *extradata = new unsigned char[36];
1622  memset(extradata, 0, 36);
1623  if (m_audioFormat.size() < 12)
1624  {
1625  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1626  "Creating decoder but haven't seen audio format.");
1627  }
1628  else
1629  {
1630  uint32_t fs = m_audioFormat[1]; // frame size
1631  extradata[12] = (fs >> 24) & 0xff;
1632  extradata[13] = (fs >> 16) & 0xff;
1633  extradata[14] = (fs >> 8) & 0xff;
1634  extradata[15] = fs & 0xff;
1635  extradata[16] = m_channels; // channels
1636  extradata[17] = m_audioFormat[3]; // sample size
1637  extradata[18] = m_audioFormat[4]; // rice_historymult
1638  extradata[19] = m_audioFormat[5]; // rice_initialhistory
1639  extradata[20] = m_audioFormat[6]; // rice_kmodifier
1640  }
1641  m_codeccontext->extradata = extradata;
1642  m_codeccontext->extradata_size = 36;
1643  m_codeccontext->channels = m_channels;
1644  if (avcodec_open2(m_codeccontext, m_codec, nullptr) < 0)
1645  {
1646  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1647  "Failed to open ALAC decoder - going silent...");
1648  DestroyDecoder();
1649  return false;
1650  }
1651  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "Opened ALAC decoder.");
1652  }
1653 
1654  return true;
1655 }
1656 
1658 {
1659  if (m_codeccontext)
1660  {
1661  avcodec_free_context(&m_codeccontext);
1662  }
1663  m_codec = nullptr;
1664 }
1665 
1667 {
1668  CloseAudioDevice();
1669 
1670  QString passthru = gCoreContext->GetBoolSetting("PassThruDeviceOverride", false)
1671  ? gCoreContext->GetSetting("PassThruOutputDevice") : QString();
1672  QString device = gCoreContext->GetSetting("AudioOutputDevice");
1673 
1674  m_audio = AudioOutput::OpenAudio(device, passthru, FORMAT_S16, m_channels,
1675  AV_CODEC_ID_NONE, m_frameRate, AUDIOOUTPUT_MUSIC,
1676  m_allowVolumeControl, false);
1677  if (!m_audio)
1678  {
1679  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1680  "Failed to open audio device. Going silent...");
1681  CloseAudioDevice();
1682  StartAudioTimer();
1683  return false;
1684  }
1685 
1686  QString error = m_audio->GetError();
1687  if (!error.isEmpty())
1688  {
1689  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1690  QString("Audio not initialised. Message was '%1'")
1691  .arg(error));
1692  CloseAudioDevice();
1693  StartAudioTimer();
1694  return false;
1695  }
1696 
1697  StopAudioTimer();
1698  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "Opened audio device.");
1699  return true;
1700 }
1701 
1703 {
1704  delete m_audio;
1705  m_audio = nullptr;
1706 }
1707 
1709 {
1710  if (m_audioTimer)
1711  return;
1712 
1713  m_audioTimer = new QTimer();
1714  connect(m_audioTimer, SIGNAL(timeout()), this, SLOT(audioRetry()));
1715  m_audioTimer->start(5000);
1716 }
1717 
1719 {
1720  if (m_audioTimer)
1721  {
1722  m_audioTimer->stop();
1723  }
1724  delete m_audioTimer;
1725  m_audioTimer = nullptr;
1726 }
1727 
1733 {
1734  if (!m_audio)
1735  return 0;
1736 
1737  auto *samples = (int16_t *)av_mallocz(AudioOutput::kMaxSizeBuffer);
1738  int frames = AUDIOCARD_BUFFER * m_frameRate / 1000;
1739  m_audio->AddData((char *)samples,
1740  frames * (m_sampleSize>>3) * m_channels,
1741  0,
1742  frames);
1743  av_free(samples);
1744  usleep(AUDIOCARD_BUFFER * 1000);
1745  uint64_t audiots = m_audio->GetAudiotime();
1746  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("AudioCardLatency: ts=%1ms")
1747  .arg(audiots));
1748  return AUDIOCARD_BUFFER - (int64_t)audiots;
1749 }
1750 
1751 void MythRAOPConnection::newEventClient(QTcpSocket *client)
1752 {
1753  LOG(VB_PLAYBACK, LOG_INFO, LOC +
1754  QString("New connection from %1:%2 for RAOP events server.")
1755  .arg(client->peerAddress().toString()).arg(client->peerPort()));
1756 
1757  m_eventClients.append(client);
1758  connect(client, SIGNAL(disconnected()), this, SLOT(deleteEventClient()));
1759 }
1760 
1762 {
1763  auto *client = dynamic_cast<QTcpSocket *>(sender());
1764  QString label;
1765 
1766  if (client != nullptr)
1767  label = QString("%1:%2").arg(client->peerAddress().toString()).arg(client->peerPort());
1768  else
1769  label = QString("unknown");
1770 
1771  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1772  QString("%1 disconnected from RAOP events server.").arg(label));
1773 }
1774 
1776 {
1777  QImage image = m_artwork.isEmpty() ? QImage() : QImage::fromData(m_artwork);
1778  int duration =
1779  lroundf(static_cast<float>(m_progressEnd-m_progressStart) / m_frameRate);
1780  int position =
1782 
1783  MythNotification *n;
1784 
1785  if (!update || !m_firstsend)
1786  {
1788  image, m_dmap, duration, position);
1789  }
1790  else
1791  {
1793  duration, position);
1794  }
1795  n->SetId(m_id);
1796  n->SetParent(this);
1797  n->SetDuration(5);
1798  n->SetFullScreen(gCoreContext->GetBoolSetting("AirPlayFullScreen"));
1800  m_firstsend = true;
1801  delete n;
1802 }
void udpDataReady(QByteArray buf, const QHostAddress &peer, quint16 port)
Socket incoming data signal handler use for audio, control and timing socket.
virtual bool IsPaused(void) const =0
QHash< QString, QString > RawHash
uint64_t framesToMs(uint64_t frames)
framesDuration Description: return the duration in ms of frames
#define RANGE_RESEND
QStringList m_incomingHeaders
void SetParent(void *parent)
contains the parent address.
virtual int GetBytesPerFrame(void) const
Definition: audiooutput.h:75
uint32_t decodeAudioPacket(uint8_t type, const QByteArray *buf, QList< AudioData > *dest)
virtual uint GetCurrentVolume(void) const
Definition: volumebase.cpp:118
void ProcessSync(const QByteArray &buf)
#define LOC
static const int kMaxSizeBuffer
kMaxSizeBuffer is the maximum size of a buffer to be used with DecodeAudio
Definition: audiooutput.h:192
#define SYNC
virtual void SetCurrentVolume(int value)
Definition: volumebase.cpp:123
static uint64_t NTPToLocal(uint32_t sec, uint32_t ticks)
ServerPool * m_clientTimingSocket
static uint64_t samples[4]
Definition: element.c:45
static QMap< QString, QString > decodeDMAP(const QByteArray &dmap)
decodeDMAP:
void ProcessRequest(const QStringList &header, const QByteArray &content)
static QString stringFromSeconds(int timeInSeconds)
stringFromSeconds:
void SetId(int id)
Optional MythNotification elements.
static void error(const char *str,...)
Definition: vbi.c:42
_NetStream(QIODevice *device)
virtual void Reset(void)=0
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
virtual int64_t LengthLastData(void) const
Definition: audiooutput.h:126
#define AUDIO_DATA
ServerPool * m_clientControlSocket
#define TIMING_REQUEST
void UnRegister(void *from, int id, bool closeimemdiately=false)
Unregister the client.
QMap< uint16_t, uint64_t > m_resends
#define TIMING_RESPONSE
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
void readClient(void)
readClient: signal handler for RAOP client connection Handle initialisation of session
QString GenerateNonce(void)
const char * formats[8]
Definition: vbilut.cpp:190
int64_t AudioCardLatency(void)
AudioCardLatency: Description: Play silence and calculate audio latency between input / output.
QMap< uint64_t, AudioPacket > m_audioQueue
#define AUDIO_BUFFER
static RSA * LoadKey(void)
LoadKey.
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...
virtual int64_t GetAudiotime(void)=0
QString GetConfDir(void)
Definition: mythdirs.cpp:224
#define AIRPLAY_HARDWARE_ID_SIZE
ServerPool * m_eventServer
#define AUDIO_RESEND
#define MAX_PACKET_SIZE
AVCodecContext * m_codeccontext
void emitTVPlaybackStopped(void)
virtual void Pause(bool paused)=0
int tryListeningPort(int baseport, int range=1)
tryListeningPort
Definition: serverpool.cpp:682
int ExpireAudio(uint64_t timestamp)
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:10
QString GetSetting(const QString &key, const QString &defaultval="")
void SetDuration(int duration)
contains a duration during which the notification will be displayed for.
unsigned short uint16_t
Definition: iso6937tables.h:1
bool Queue(const MythNotification &notification)
Queue a notification Queue() is thread-safe and can be called from anywhere.
static void FinishResponse(_NetStream *stream, QTcpSocket *socket, QString &option, QString &cseq, QString &responseData)
_NetStream & operator<<(const QString &str)
QHostAddress m_peerAddress
void SendNotification(bool update=false)
static RawHash FindTags(const QStringList &lines)
int FILE
Definition: mythburn.py:110
Manages a collection of sockets listening on different ports.
Definition: serverpool.h:59
QByteArray DigestMd5Response(const QString &response, const QString &option, const QString &nonce, const QString &password, QByteArray &auth)
static QString g_rsaLastError
int Register(void *from)
An application can register in which case it will be assigned a reusable screen, which can be modifie...
PictureAttribute next(PictureAttributeSupported supported, PictureAttribute attribute)
MythRAOPConnection(QObject *parent, QTcpSocket *socket, QByteArray id, int port)
void SendTimeRequest(void)
SendTimeRequest: Send a time request to the RAOP client.
void FinishAuthenticationResponse(_NetStream *stream, QTcpSocket *socket, QString &cseq)
virtual bool AddData(void *buffer, int len, int64_t timecode, int frames)=0
Add data to the audiobuffer for playback.
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
bool GetBoolSetting(const QString &key, bool defaultval=false)
qint64 writeDatagram(const char *data, qint64 size, const QHostAddress &addr, quint16 port)
Definition: serverpool.cpp:589
void SendResendRequest(uint64_t timestamp, uint16_t expected, uint16_t got)
SendResendRequest: Request RAOP client to resend missed RTP packets.
static bool GetPacketType(const QByteArray &buf, uint8_t &type, uint16_t &seq, uint64_t &timestamp)
void ExpireResendRequests(uint64_t timestamp)
ExpireResendRequests: Expire resend requests that are older than timestamp.
void close(void)
Definition: serverpool.cpp:361
QList< QTcpSocket * > m_eventClients
int tryBindingPort(int baseport, int range=1)
tryBindingPort
Definition: serverpool.cpp:715
void ProcessTimeResponse(const QByteArray &buf)
ProcessTimeResponse: Calculate the network latency, we do not use the reference time send by itunes i...
#define FIRSTAUDIO_DATA
const char * frames[3]
Definition: element.c:46
static QStringList splitLines(const QByteArray &lines)
#define FIRSTSYNC
void SetFullScreen(bool f)
a notification may request to be displayed in full screen, this request may not be fullfilled should ...
void av_free(void *ptr)
uint8_t * data
#define RAOP_PORT_RANGE
static Type Update
void WantingPlayback(QObject *sender)
All the objects that have registered using MythCoreContext::RegisterForPlayback but sender will be ca...
QString GetError(void) const
Definition: audiooutput.h:143
#define AUDIOCARD_BUFFER
MythNotificationCenter * GetNotificationCenter(void)
virtual int64_t GetAudioBufferedTime(void)
report amount of audio buffered in milliseconds.
Definition: audiooutput.h:139
void newEventClient(QTcpSocket *client)
QList< int > m_audioFormat