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 = 0;
239  uint16_t seq = 0;
240  uint64_t timestamp = 0;
241 
242  if (!GetPacketType(buf, type, seq, timestamp))
243  {
244  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
245  QString("Packet doesn't start with valid Rtp Header (0x%1)")
246  .arg((uint8_t)buf[0], 0, 16));
247  return;
248  }
249 
250  switch (type)
251  {
252  case SYNC:
253  case FIRSTSYNC:
254  ProcessSync(buf);
255  ProcessAudio();
256  return;
257 
258  case FIRSTAUDIO_DATA:
259  m_nextSequence = seq;
260  m_nextTimestamp = timestamp;
261  // With iTunes we know what the first sequence is going to be.
262  // iOS device do not tell us before streaming start what the first
263  // packet is going to be.
264  m_streamingStarted = true;
265  break;
266 
267  case AUDIO_DATA:
268  case AUDIO_RESEND:
269  break;
270 
271  case TIMING_RESPONSE:
272  ProcessTimeResponse(buf);
273  return;
274 
275  default:
276  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
277  QString("Packet type (0x%1) not handled")
278  .arg(type, 0, 16));
279  return;
280  }
281 
282  timestamp = framesToMs(timestamp);
283  if (timestamp < m_currentTimestamp)
284  {
285  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
286  QString("Received packet %1 too late, ignoring")
287  .arg(seq));
288  return;
289  }
290  // regular data packet
291  if (type == AUDIO_DATA || type == FIRSTAUDIO_DATA)
292  {
293  if (m_streamingStarted && seq != m_nextSequence)
294  SendResendRequest(timestamp, m_nextSequence, seq);
295 
296  m_nextSequence = seq + 1;
297  m_nextTimestamp = timestamp;
298  m_streamingStarted = true;
299  }
300 
301  if (!m_streamingStarted)
302  return;
303 
304  // resent packet
305  if (type == AUDIO_RESEND)
306  {
307  if (m_resends.contains(seq))
308  {
309  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
310  QString("Received required resend %1 (with ts:%2 last:%3)")
311  .arg(seq).arg(timestamp).arg(m_nextSequence));
312  m_resends.remove(seq);
313  }
314  else
315  {
316  LOG(VB_PLAYBACK, LOG_WARNING, LOC +
317  QString("Received unexpected resent packet %1")
318  .arg(seq));
319  }
320  }
321 
322  // Check that the audio packet is valid, do so by decoding it. If an error
323  // occurs, ask to resend it
324  auto *decoded = new QList<AudioData>();
325  int numframes = decodeAudioPacket(type, &buf, decoded);
326  if (numframes < 0)
327  {
328  // an error occurred, ask for the audio packet once again.
329  LOG(VB_PLAYBACK, LOG_ERR, LOC + QString("Error decoding audio"));
330  SendResendRequest(timestamp, seq, seq+1);
331  delete decoded;
332  return;
333  }
334  AudioPacket frames { seq, decoded };
335  m_audioQueue.insert(timestamp, frames);
336  ProcessAudio();
337 }
338 
339 void MythRAOPConnection::ProcessSync(const QByteArray &buf)
340 {
341  bool first = (uint8_t)buf[0] == 0x90; // First sync is 0x90,0x55
342  const char *req = buf.constData();
343  uint64_t current_ts = qFromBigEndian(*(uint32_t *)(req + 4));
344  uint64_t next_ts = qFromBigEndian(*(uint32_t *)(req + 16));
345 
346  uint64_t current = framesToMs(current_ts);
347  uint64_t next = framesToMs(next_ts);
348 
352 
353  if (current_ts > m_progressStart)
354  {
355  m_progressCurrent = next_ts;
356  SendNotification(true);
357  }
358 
359  if (first)
360  {
361  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("Receiving first SYNC packet"));
362  }
363  else
364  {
365  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("Receiving SYNC packet"));
366  }
367 
368  timeval t {};
369  gettimeofday(&t, nullptr);
370  m_timeLastSync = t.tv_sec * 1000 + t.tv_usec / 1000;
371 
372  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("SYNC: cur:%1 next:%2 time:%3")
374 
375  int64_t delay = framesToMs((uint64_t)m_audioQueue.size() * m_framesPerPacket);
376  int64_t audiots = m_audio->GetAudiotime();
377  int64_t currentLatency = 0LL;
378 
379  if (m_audioStarted)
380  {
381  currentLatency = audiots - (int64_t)m_currentTimestamp;
382  }
383 
384  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
385  QString("RAOP timestamps: about to play:%1 desired:%2 latency:%3")
386  .arg(audiots).arg(m_currentTimestamp)
387  .arg(currentLatency));
388 
389  delay += m_audio->GetAudioBufferedTime();
390  delay += currentLatency;
391 
392  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
393  QString("Queue=%1 buffer=%2ms ideal=%3ms diffts:%4ms")
394  .arg(m_audioQueue.size())
395  .arg(delay)
396  .arg(m_bufferLength)
397  .arg(m_bufferLength-delay));
398 
399  if (m_adjustedLatency <= 0 && m_audioStarted &&
400  (-currentLatency > AUDIOCARD_BUFFER))
401  {
402  // Too much delay in playback.
403  // The threshold is a value chosen to be loose enough so it doesn't
404  // trigger too often, but should be low enough to detect any accidental
405  // interruptions.
406  // Will drop some frames in next ProcessAudio
407  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
408  QString("Too much delay (%1ms), adjusting")
409  .arg(m_bufferLength - delay));
410 
412 
413  // Expire old audio
416  if (res > 0)
417  {
418  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("Drop %1 packets").arg(res));
419  }
420 
421  m_audioStarted = false;
422  }
423 }
424 
430  uint16_t expected, uint16_t got)
431 {
433  return;
434 
435  int16_t missed = (got < expected) ?
436  (int16_t)(((int32_t)got + UINT16_MAX + 1) - expected) :
437  got - expected;
438 
439  LOG(VB_PLAYBACK, LOG_INFO, LOC +
440  QString("Missed %1 packet(s): expected %2 got %3 ts:%4")
441  .arg(missed).arg(expected).arg(got).arg(timestamp));
442 
443  unsigned char req[8];
444  req[0] = 0x80;
445  req[1] = RANGE_RESEND | 0x80;
446  *(uint16_t *)(req + 2) = qToBigEndian(m_seqNum++);
447  *(uint16_t *)(req + 4) = qToBigEndian(expected); // missed seqnum
448  *(uint16_t *)(req + 6) = qToBigEndian(missed); // count
449 
450  if (m_clientControlSocket->writeDatagram((char *)req, sizeof(req),
452  == sizeof(req))
453  {
454  for (uint16_t count = 0; count < missed; count++)
455  {
456  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Sent resend for %1")
457  .arg(expected + count));
458  m_resends.insert(expected + count, timestamp);
459  }
460  }
461  else
462  LOG(VB_PLAYBACK, LOG_ERR, LOC + "Failed to send resend request.");
463 }
464 
471 {
472  if (m_resends.isEmpty())
473  return;
474 
475  QMutableMapIterator<uint16_t,uint64_t> it(m_resends);
476  while (it.hasNext())
477  {
478  it.next();
479  if (it.value() < timestamp && m_streamingStarted)
480  {
481  LOG(VB_PLAYBACK, LOG_WARNING, LOC +
482  QString("Never received resend packet %1").arg(it.key()));
483  m_resends.remove(it.key());
484  }
485  }
486 }
487 
493 {
494  if (!m_clientControlSocket) // should never happen
495  return;
496 
497  timeval t {};
498  gettimeofday(&t, nullptr);
499 
500  unsigned char req[32];
501  req[0] = 0x80;
502  req[1] = TIMING_REQUEST | 0x80;
503  // this is always 0x00 0x07 according to http://blog.technologeek.org/airtunes-v2
504  // no other value works
505  req[2] = 0x00;
506  req[3] = 0x07;
507  *(uint32_t *)(req + 4) = (uint32_t)0;
508  *(uint64_t *)(req + 8) = (uint64_t)0;
509  *(uint64_t *)(req + 16) = (uint64_t)0;
510  *(uint32_t *)(req + 24) = qToBigEndian((uint32_t)t.tv_sec);
511  *(uint32_t *)(req + 28) = qToBigEndian((uint32_t)t.tv_usec);
512 
513  if (m_clientTimingSocket->writeDatagram((char *)req, sizeof(req), m_peerAddress,
514  m_clientTimingPort) != sizeof(req))
515  {
516  LOG(VB_PLAYBACK, LOG_ERR, LOC + "Failed to send resend time request.");
517  return;
518  }
519  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
520  QString("Requesting master time (Local %1.%2)")
521  .arg(t.tv_sec).arg(t.tv_usec));
522 }
523 
530 void MythRAOPConnection::ProcessTimeResponse(const QByteArray &buf)
531 {
532  timeval t1 {};
533  timeval t2 {};
534  const char *req = buf.constData();
535 
536  t1.tv_sec = qFromBigEndian(*(uint32_t *)(req + 8));
537  t1.tv_usec = qFromBigEndian(*(uint32_t *)(req + 12));
538 
539  gettimeofday(&t2, nullptr);
540  uint64_t time1 = t1.tv_sec * 1000 + t1.tv_usec / 1000;
541  uint64_t time2 = t2.tv_sec * 1000 + t2.tv_usec / 1000;
542  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("Read back time (Local %1.%2)")
543  .arg(t1.tv_sec).arg(t1.tv_usec));
544  // network latency equal time difference in ms between request and response
545  // divide by two for approximate time of one way trip
546  m_networkLatency = (time2 - time1) / 2;
547  LOG(VB_AUDIO, LOG_DEBUG, LOC + QString("Network Latency: %1ms")
548  .arg(m_networkLatency));
549 
550  // now calculate the time difference between the client and us.
551  // this is NTP time, where sec is in seconds, and ticks is in 1/2^32s
552  uint32_t sec = qFromBigEndian(*(uint32_t *)(req + 24));
553  uint32_t ticks = qFromBigEndian(*(uint32_t *)(req + 28));
554  // convert ticks into ms
555  int64_t master = NTPToLocal(sec, ticks);
556  m_clockSkew = master - time2;
557 }
558 
559 uint64_t MythRAOPConnection::NTPToLocal(uint32_t sec, uint32_t ticks)
560 {
561  return (int64_t)sec * 1000LL + (((int64_t)ticks * 1000LL) >> 32);
562 }
563 
564 bool MythRAOPConnection::GetPacketType(const QByteArray &buf, uint8_t &type,
565  uint16_t &seq, uint64_t &timestamp)
566 {
567  // All RAOP packets start with | 0x80/0x90 (first sync) | PACKET_TYPE |
568  if ((uint8_t)buf[0] != 0x80 && (uint8_t)buf[0] != 0x90)
569  {
570  return false;
571  }
572 
573  type = buf[1];
574  // Is it first sync packet?
575  if ((uint8_t)buf[0] == 0x90 && type == FIRSTSYNC)
576  {
577  return true;
578  }
579  if (type != FIRSTAUDIO_DATA)
580  {
581  type &= ~0x80;
582  }
583 
585  return true;
586 
587  const char *ptr = buf.constData();
588  if (type == AUDIO_RESEND)
589  {
590  ptr += 4;
591  }
592  seq = qFromBigEndian(*(uint16_t *)(ptr + 2));
593  timestamp = qFromBigEndian(*(uint32_t *)(ptr + 4));
594  return true;
595 }
596 
597 // Audio decode / playback related routines
598 
600  const QByteArray *buf,
601  QList<AudioData> *dest)
602 {
603  const char *data_in = buf->constData();
604  int len = buf->size();
605  if (type == AUDIO_RESEND)
606  {
607  data_in += 4;
608  len -= 4;
609  }
610  data_in += 12;
611  len -= 12;
612  if (len < 16)
613  return -1;
614 
615  int aeslen = len & ~0xf;
616  unsigned char iv[16];
617  unsigned char decrypted_data[MAX_PACKET_SIZE];
618  memcpy(iv, m_aesIV.constData(), sizeof(iv));
619  AES_cbc_encrypt((const unsigned char *)data_in,
620  decrypted_data, aeslen,
621  &m_aesKey, iv, AES_DECRYPT);
622  memcpy(decrypted_data + aeslen, data_in + aeslen, len - aeslen);
623 
624  AVPacket tmp_pkt;
625  AVCodecContext *ctx = m_codecContext;
626 
627  av_init_packet(&tmp_pkt);
628  tmp_pkt.data = decrypted_data;
629  tmp_pkt.size = len;
630 
631  uint32_t frames_added = 0;
632  auto *samples = (uint8_t *)av_mallocz(AudioOutput::kMaxSizeBuffer);
633  while (tmp_pkt.size > 0)
634  {
635  int data_size = 0;
636  int ret = AudioOutputUtil::DecodeAudio(ctx, samples,
637  data_size, &tmp_pkt);
638  if (ret < 0)
639  {
640  av_free(samples);
641  return -1;
642  }
643 
644  if (data_size)
645  {
646  int num_samples = data_size /
647  (ctx->channels * av_get_bytes_per_sample(ctx->sample_fmt));
648 
649  frames_added += num_samples;
650  AudioData block {samples, data_size, num_samples};
651  dest->append(block);
652  }
653  tmp_pkt.data += ret;
654  tmp_pkt.size -= ret;
655  }
656  return frames_added;
657 }
658 
660 {
661  if (!m_streamingStarted || !m_audio)
662  return;
663 
664  if (m_audio->IsPaused())
665  {
666  // ALSA takes a while to unpause, enough to have SYNC starting to drop
667  // packets, so unpause as early as possible
668  m_audio->Pause(false);
669  }
670  timeval t {};
671  gettimeofday(&t, nullptr);
672  uint64_t dtime = (t.tv_sec * 1000 + t.tv_usec / 1000) - m_timeLastSync;
673  uint64_t rtp = dtime + m_currentTimestamp;
674  uint64_t buffered = m_audioStarted ? m_audio->GetAudioBufferedTime() : 0;
675 
676  // Keep audio framework buffer as short as possible, keeping everything in
677  // m_audioQueue, so we can easily reset the least amount possible
678  if (buffered > AUDIOCARD_BUFFER)
679  return;
680 
681  // Also make sure m_audioQueue never goes to less than 1/3 of the RDP stream
682  // total latency, this should gives us enough time to receive missed packets
683  int64_t queue = framesToMs((uint64_t)m_audioQueue.size() * m_framesPerPacket);
684  if (queue < m_bufferLength / 3)
685  return;
686 
687  rtp += buffered;
688 
689  // How many packets to add to the audio card, to fill AUDIOCARD_BUFFER
690  int max_packets = ((AUDIOCARD_BUFFER - buffered)
691  * m_frameRate / 1000) / m_framesPerPacket;
692  int i = 0;
693  uint64_t timestamp = 0;
694 
695  QMapIterator<uint64_t,AudioPacket> packet_it(m_audioQueue);
696  while (packet_it.hasNext() && i <= max_packets)
697  {
698  packet_it.next();
699 
700  timestamp = packet_it.key();
701  if (timestamp < rtp)
702  {
703  if (!m_audioStarted)
704  {
705  m_audio->Reset(); // clear audio card
706  }
707  AudioPacket frames = packet_it.value();
708 
709  if (m_lastSequence != frames.seq)
710  {
711  LOG(VB_PLAYBACK, LOG_ERR, LOC +
712  QString("Audio discontinuity seen. Played %1 (%3) expected %2")
713  .arg(frames.seq).arg(m_lastSequence).arg(timestamp));
714  m_lastSequence = frames.seq;
715  }
716  m_lastSequence++;
717 
718  foreach (auto & data, *frames.data)
719  {
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  foreach (auto & data, *frames.data)
771  av_free(data.data);
772  delete frames.data;
773  }
774  m_audioQueue.remove(packet_it.key());
775  res++;
776  }
777  }
778  return res;
779 }
780 
782 {
783  if (m_audio)
784  {
785  m_audio->Reset();
786  }
787  ExpireAudio(UINT64_MAX);
788  ExpireResendRequests(UINT64_MAX);
789  m_audioStarted = false;
790 }
791 
793 {
794  LOG(VB_PLAYBACK, LOG_INFO, LOC + "Closing connection after inactivity.");
795  m_socket->disconnectFromHost();
796 }
797 
799 {
800  if (!m_audio && OpenAudioDevice())
801  {
802  CreateDecoder();
803  }
804 
805  if (m_audio && m_codec && m_codecContext)
806  {
807  StopAudioTimer();
808  }
809 }
810 
816 {
817  auto *socket = dynamic_cast<QTcpSocket *>(sender());
818  if (!socket)
819  return;
820 
821  QByteArray data = socket->readAll();
822  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("readClient(%1): ")
823  .arg(data.size()) + data.constData());
824 
825  // For big content, we may be called several times for a single packet
826  if (!m_incomingPartial)
827  {
828  m_incomingHeaders.clear();
829  m_incomingContent.clear();
830  m_incomingSize = 0;
831 
832  QTextStream stream(data);
833  QString line;
834  do
835  {
836  line = stream.readLine();
837  if (line.size() == 0)
838  break;
839  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("Header(%1) = %2")
840  .arg(m_socket->peerAddress().toString())
841  .arg(line));
842  m_incomingHeaders.append(line);
843  if (line.contains("Content-Length:"))
844  {
845  m_incomingSize = line.mid(line.indexOf(" ") + 1).toInt();
846  }
847  }
848  while (!line.isNull());
849 
850  if (m_incomingHeaders.empty())
851  return;
852 
853  if (!stream.atEnd())
854  {
855  int pos = stream.pos();
856  if (pos > 0)
857  {
858  m_incomingContent.append(data.mid(pos));
859  }
860  }
861  }
862  else
863  {
864  m_incomingContent.append(data);
865  }
866 
867  // If we haven't received all the content yet, wait (see when receiving
868  // coverart
869  if (m_incomingContent.size() < m_incomingSize)
870  {
871  m_incomingPartial = true;
872  return;
873  }
874  m_incomingPartial = false;
875  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("Content(%1) = %2")
876  .arg(m_incomingContent.size()).arg(m_incomingContent.constData()));
877 
879 }
880 
881 void MythRAOPConnection::ProcessRequest(const QStringList &header,
882  const QByteArray &content)
883 {
884  if (header.isEmpty())
885  return;
886 
887  RawHash tags = FindTags(header);
888 
889  if (!tags.contains("CSeq"))
890  {
891  LOG(VB_PLAYBACK, LOG_ERR, LOC + "ProcessRequest: Didn't find CSeq");
892  return;
893  }
894 
895  QString option = header[0].left(header[0].indexOf(" "));
896 
897  // process RTP-info field
898  bool gotRTP = false;
899  uint16_t RTPseq = 0;
900  uint64_t RTPtimestamp = 0;
901  if (tags.contains("RTP-Info"))
902  {
903  gotRTP = true;
904  QString data = tags["RTP-Info"];
905  QStringList items = data.split(";");
906  foreach (QString item, items)
907  {
908  if (item.startsWith("seq"))
909  {
910  RTPseq = item.mid(item.indexOf("=") + 1).trimmed().toUShort();
911  }
912  else if (item.startsWith("rtptime"))
913  {
914  RTPtimestamp = item.mid(item.indexOf("=") + 1).trimmed().toUInt();
915  }
916  }
917  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("RTP-Info: seq=%1 rtptime=%2")
918  .arg(RTPseq).arg(RTPtimestamp));
919  }
920 
921  if (gCoreContext->GetBoolSetting("AirPlayPasswordEnabled", false))
922  {
923  if (m_nonce.isEmpty())
924  {
926  }
927  if (!tags.contains("Authorization"))
928  {
929  // 60 seconds to enter password.
930  m_watchdogTimer->start(60000);
932  return;
933  }
934 
935  QByteArray auth;
936  if (DigestMd5Response(tags["Authorization"], option, m_nonce,
937  gCoreContext->GetSetting("AirPlayPassword"),
938  auth) == auth)
939  {
940  LOG(VB_PLAYBACK, LOG_INFO, LOC + "RAOP client authenticated");
941  }
942  else
943  {
944  LOG(VB_PLAYBACK, LOG_INFO, LOC + "RAOP authentication failed");
946  return;
947  }
948  }
949  *m_textStream << "RTSP/1.0 200 OK\r\n";
950 
951  if (tags.contains("Apple-Challenge"))
952  {
953  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("Received Apple-Challenge"));
954 
955  *m_textStream << "Apple-Response: ";
956  if (!LoadKey())
957  return;
958  int tosize = RSA_size(LoadKey());
959  auto *to = new uint8_t[tosize];
960 
961  QByteArray challenge =
962  QByteArray::fromBase64(tags["Apple-Challenge"].toLatin1());
963  int challenge_size = challenge.size();
964  if (challenge_size != 16)
965  {
966  LOG(VB_PLAYBACK, LOG_ERR, LOC +
967  QString("Decoded challenge size %1, expected 16")
968  .arg(challenge_size));
969  if (challenge_size > 16)
970  challenge_size = 16;
971  }
972 
973  int i = 0;
974  unsigned char from[38];
975  memcpy(from, challenge.constData(), challenge_size);
976  i += challenge_size;
977  if (m_socket->localAddress().protocol() ==
978  QAbstractSocket::IPv4Protocol)
979  {
980  uint32_t ip = m_socket->localAddress().toIPv4Address();
981  ip = qToBigEndian(ip);
982  memcpy(from + i, &ip, 4);
983  i += 4;
984  }
985  else if (m_socket->localAddress().protocol() ==
986  QAbstractSocket::IPv6Protocol)
987  {
988  Q_IPV6ADDR ip = m_socket->localAddress().toIPv6Address();
989  if(memcmp(&ip,
990  "\x00\x00\x00\x00" "\x00\x00\x00\x00" "\x00\x00\xff\xff",
991  12) == 0)
992  {
993  memcpy(from + i, &ip[12], 4);
994  i += 4;
995  }
996  else
997  {
998  memcpy(from + i, &ip, 16);
999  i += 16;
1000  }
1001  }
1002  memcpy(from + i, m_hardwareId.constData(), AIRPLAY_HARDWARE_ID_SIZE);
1004 
1005  int pad = 32 - i;
1006  if (pad > 0)
1007  {
1008  memset(from + i, 0, pad);
1009  i += pad;
1010  }
1011 
1012  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1013  QString("Full base64 response: '%1' size %2")
1014  .arg(QByteArray((const char *)from, i).toBase64().constData())
1015  .arg(i));
1016 
1017  RSA_private_encrypt(i, from, to, LoadKey(), RSA_PKCS1_PADDING);
1018 
1019  QByteArray base64 = QByteArray((const char *)to, tosize).toBase64();
1020  delete[] to;
1021 
1022  for (int pos = base64.size() - 1; pos > 0; pos--)
1023  {
1024  if (base64[pos] == '=')
1025  base64[pos] = ' ';
1026  else
1027  break;
1028  }
1029  LOG(VB_PLAYBACK, LOG_DEBUG, QString("tSize=%1 tLen=%2 tResponse=%3")
1030  .arg(tosize).arg(base64.size()).arg(base64.constData()));
1031  *m_textStream << base64.trimmed() << "\r\n";
1032  }
1033 
1034  QString responseData;
1035  if (option == "OPTIONS")
1036  {
1037  *m_textStream << "Public: ANNOUNCE, SETUP, RECORD, PAUSE, FLUSH, "
1038  "TEARDOWN, OPTIONS, GET_PARAMETER, SET_PARAMETER, POST, GET\r\n";
1039  }
1040  else if (option == "ANNOUNCE")
1041  {
1042  QStringList lines = splitLines(content);
1043  foreach (QString line, lines)
1044  {
1045  if (line.startsWith("a=rsaaeskey:"))
1046  {
1047  QString key = line.mid(12).trimmed();
1048  QByteArray decodedkey = QByteArray::fromBase64(key.toLatin1());
1049  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1050  QString("RSAAESKey: %1 (decoded size %2)")
1051  .arg(key).arg(decodedkey.size()));
1052 
1053  if (LoadKey())
1054  {
1055  int size = sizeof(char) * RSA_size(LoadKey());
1056  char *decryptedkey = new char[size];
1057  if (RSA_private_decrypt(decodedkey.size(),
1058  (const unsigned char *)decodedkey.constData(),
1059  (unsigned char *)decryptedkey,
1060  LoadKey(), RSA_PKCS1_OAEP_PADDING))
1061  {
1062  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1063  "Successfully decrypted AES key from RSA.");
1064  AES_set_decrypt_key((const unsigned char *)decryptedkey,
1065  128, &m_aesKey);
1066  }
1067  else
1068  {
1069  LOG(VB_PLAYBACK, LOG_WARNING, LOC +
1070  "Failed to decrypt AES key from RSA.");
1071  }
1072  delete [] decryptedkey;
1073  }
1074  }
1075  else if (line.startsWith("a=aesiv:"))
1076  {
1077  QString aesiv = line.mid(8).trimmed();
1078  m_aesIV = QByteArray::fromBase64(aesiv.toLatin1());
1079  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1080  QString("AESIV: %1 (decoded size %2)")
1081  .arg(aesiv).arg(m_aesIV.size()));
1082  }
1083  else if (line.startsWith("a=fmtp:"))
1084  {
1085  m_audioFormat.clear();
1086  QString format = line.mid(7).trimmed();
1087  QList<QString> formats = format.split(' ');
1088  foreach (QString fmt, formats)
1089  m_audioFormat.append(fmt.toInt());
1090 
1091  foreach (int fmt, m_audioFormat)
1092  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1093  QString("Audio parameter: %1").arg(fmt));
1097  m_frameRate = m_audioFormat[11];
1098  }
1099  }
1100  }
1101  else if (option == "SETUP")
1102  {
1103  if (tags.contains("Transport"))
1104  {
1105  // New client is trying to play audio, disconnect all the other clients
1106  auto dev = dynamic_cast<MythRAOPDevice*>(parent());
1107  if (dev != nullptr)
1108  dev->DeleteAllClients(this);
1109  gCoreContext->WantingPlayback(parent());
1110  m_playbackStarted = true;
1111 
1112  int control_port = 0;
1113  int timing_port = 0;
1114  QString data = tags["Transport"];
1115  QStringList items = data.split(";");
1116  bool events = false;
1117 
1118  foreach (QString item, items)
1119  {
1120  if (item.startsWith("control_port"))
1121  control_port = item.mid(item.indexOf("=") + 1).trimmed().toUInt();
1122  else if (item.startsWith("timing_port"))
1123  timing_port = item.mid(item.indexOf("=") + 1).trimmed().toUInt();
1124  else if (item.startsWith("events"))
1125  events = true;
1126  }
1127 
1128  LOG(VB_PLAYBACK, LOG_INFO, LOC +
1129  QString("Negotiated setup with client %1 on port %2")
1130  .arg(m_socket->peerAddress().toString())
1131  .arg(m_socket->peerPort()));
1132  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1133  QString("control port: %1 timing port: %2")
1134  .arg(control_port).arg(timing_port));
1135 
1136  m_peerAddress = m_socket->peerAddress();
1137 
1139  {
1140  m_clientControlSocket->disconnect();
1142  delete m_clientControlSocket;
1143  }
1144 
1145  m_clientControlSocket = new ServerPool(this);
1146  int controlbind_port =
1147  m_clientControlSocket->tryBindingPort(control_port,
1148  RAOP_PORT_RANGE);
1149  if (controlbind_port < 0)
1150  {
1151  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1152  QString("Failed to bind to client control port. "
1153  "Control of audio stream may fail"));
1154  }
1155  else
1156  {
1157  LOG(VB_PLAYBACK, LOG_INFO, LOC +
1158  QString("Bound to client control port %1 on port %2")
1159  .arg(control_port).arg(controlbind_port));
1160  }
1161  m_clientControlPort = control_port;
1162  connect(m_clientControlSocket,
1163  SIGNAL(newDatagram(QByteArray, QHostAddress, quint16)),
1164  this,
1165  SLOT(udpDataReady(QByteArray, QHostAddress, quint16)));
1166 
1168  {
1169  m_clientTimingSocket->disconnect();
1171  delete m_clientTimingSocket;
1172  }
1173 
1174  m_clientTimingSocket = new ServerPool(this);
1175  int timingbind_port =
1176  m_clientTimingSocket->tryBindingPort(timing_port,
1177  RAOP_PORT_RANGE);
1178  if (timingbind_port < 0)
1179  {
1180  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1181  QString("Failed to bind to client timing port. "
1182  "Timing of audio stream will be incorrect"));
1183  }
1184  else
1185  {
1186  LOG(VB_PLAYBACK, LOG_INFO, LOC +
1187  QString("Bound to client timing port %1 on port %2")
1188  .arg(timing_port).arg(timingbind_port));
1189  }
1190  m_clientTimingPort = timing_port;
1191  connect(m_clientTimingSocket,
1192  SIGNAL(newDatagram(QByteArray, QHostAddress, quint16)),
1193  this,
1194  SLOT(udpDataReady(QByteArray, QHostAddress, quint16)));
1195 
1196  if (m_eventServer)
1197  {
1198  // Should never get here, but just in case
1199  foreach (auto client, m_eventClients)
1200  {
1201  client->disconnect();
1202  client->abort();
1203  delete client;
1204  }
1205  m_eventClients.clear();
1206  m_eventServer->disconnect();
1207  m_eventServer->close();
1208  delete m_eventServer;
1209  m_eventServer = nullptr;
1210  }
1211 
1212  if (events)
1213  {
1214  m_eventServer = new ServerPool(this);
1216  RAOP_PORT_RANGE);
1217  if (m_eventPort < 0)
1218  {
1219  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1220  "Failed to find a port for RAOP events server.");
1221  }
1222  else
1223  {
1224  LOG(VB_PLAYBACK, LOG_INFO, LOC +
1225  QString("Listening for RAOP events on port %1").arg(m_eventPort));
1226  connect(m_eventServer, SIGNAL(newConnection(QTcpSocket *)),
1227  this, SLOT(newEventClient(QTcpSocket *)));
1228  }
1229  }
1230 
1231  if (OpenAudioDevice())
1232  {
1233  CreateDecoder();
1234  // Calculate audio card latency
1235  if (m_cardLatency < 0)
1236  {
1238  // if audio isn't started, start playing 500ms worth of silence
1239  // and measure timestamp difference
1240  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1241  QString("Audio hardware latency: %1ms")
1243  }
1244  }
1245 
1246  // Recreate transport line with new ports value
1247  QString newdata;
1248  bool first = true;
1249  foreach (QString item, items)
1250  {
1251  if (!first)
1252  {
1253  newdata += ";";
1254  }
1255  if (item.startsWith("control_port"))
1256  {
1257  newdata += "control_port=" + QString::number(controlbind_port);
1258  }
1259  else if (item.startsWith("timing_port"))
1260  {
1261  newdata += "timing_port=" + QString::number(timingbind_port);
1262  }
1263  else if (item.startsWith("events"))
1264  {
1265  newdata += "event_port=" + QString::number(m_eventPort);
1266  }
1267  else
1268  {
1269  newdata += item;
1270  }
1271  first = false;
1272  }
1273  if (!first)
1274  {
1275  newdata += ";";
1276  }
1277  newdata += "server_port=" + QString::number(m_dataPort);
1278 
1279  *m_textStream << "Transport: " << newdata << "\r\n";
1280  *m_textStream << "Session: 1\r\n";
1281  *m_textStream << "Audio-Jack-Status: connected\r\n";
1282 
1283  // Ask for master clock value to determine time skew and average network latency
1284  SendTimeRequest();
1285  }
1286  else
1287  {
1288  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1289  "No Transport details found - Ignoring");
1290  }
1291  }
1292  else if (option == "RECORD")
1293  {
1294  if (gotRTP)
1295  {
1296  m_nextSequence = RTPseq;
1297  m_nextTimestamp = RTPtimestamp;
1298  }
1299 
1300  // Calculate audio card latency
1301  if (m_cardLatency > 0)
1302  {
1303  *m_textStream << QString("Audio-Latency: %1")
1305  }
1306  }
1307  else if (option == "FLUSH")
1308  {
1309  if (gotRTP)
1310  {
1311  m_nextSequence = RTPseq;
1312  m_nextTimestamp = framesToMs(RTPtimestamp);
1314  }
1315  // determine RTP timestamp of last sample played
1316  uint64_t timestamp = m_audioStarted && m_audio ?
1318  *m_textStream << "RTP-Info: rtptime=" << QString::number(timestamp);
1319  m_streamingStarted = false;
1320  ResetAudio();
1321  }
1322  else if (option == "SET_PARAMETER")
1323  {
1324  if (tags.contains("Content-Type"))
1325  {
1326  if (tags["Content-Type"] == "text/parameters")
1327  {
1328  QString name = content.left(content.indexOf(":"));
1329  QString param = content.mid(content.indexOf(":") + 1).trimmed();
1330 
1331  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1332  QString("text/parameters: name=%1 parem=%2")
1333  .arg(name).arg(param));
1334 
1335  if (name == "volume" && m_allowVolumeControl && m_audio)
1336  {
1337  float volume = (param.toFloat() + 30.0F) * 100.0F / 30.0F;
1338  if (volume < 0.01F)
1339  volume = 0.0F;
1340  LOG(VB_PLAYBACK, LOG_INFO,
1341  LOC + QString("Setting volume to %1 (raw %3)")
1342  .arg(volume).arg(param));
1343  m_audio->SetCurrentVolume((int)volume);
1344  }
1345  else if (name == "progress")
1346  {
1347  QStringList items = param.split("/");
1348  if (items.size() == 3)
1349  {
1350  m_progressStart = items[0].toUInt();
1351  m_progressCurrent = items[1].toUInt();
1352  m_progressEnd = items[2].toUInt();
1353  }
1354  int length =
1356  int current =
1358 
1359  LOG(VB_PLAYBACK, LOG_INFO,
1360  LOC +QString("Progress: %1/%2")
1361  .arg(stringFromSeconds(current))
1362  .arg(stringFromSeconds(length)));
1363  SendNotification(true);
1364  }
1365  }
1366  else if(tags["Content-Type"] == "image/none")
1367  {
1368  m_artwork.clear();
1369  SendNotification(false);
1370  }
1371  else if(tags["Content-Type"].startsWith("image/"))
1372  {
1373  // Receiving image coverart
1374  m_artwork = content;
1375  SendNotification(false);
1376  }
1377  else if (tags["Content-Type"] == "application/x-dmap-tagged")
1378  {
1379  // Receiving DMAP metadata
1381  LOG(VB_PLAYBACK, LOG_INFO,
1382  QString("Receiving Title:%1 Artist:%2 Album:%3 Format:%4")
1383  .arg(m_dmap["minm"]).arg(m_dmap["asar"])
1384  .arg(m_dmap["asal"]).arg(m_dmap["asfm"]));
1385  SendNotification(false);
1386  }
1387  }
1388  }
1389  else if (option == "GET_PARAMETER")
1390  {
1391  if (tags.contains("Content-Type"))
1392  {
1393  if (tags["Content-Type"] == "text/parameters")
1394  {
1395  QStringList lines = splitLines(content);
1396  *m_textStream << "Content-Type: text/parameters\r\n";
1397  foreach (QString line, lines)
1398  {
1399  if (line == "volume")
1400  {
1401  int volume = (m_allowVolumeControl && m_audio) ?
1402  m_audio->GetCurrentVolume() : 0;
1403  responseData += QString("volume: %1\r\n")
1404  .arg(volume * 30.0F / 100.0F - 30.0F,1,'f',6,'0');
1405  }
1406  }
1407  }
1408  }
1409  }
1410  else if (option == "TEARDOWN")
1411  {
1412  m_socket->disconnectFromHost();
1413  return;
1414  }
1415  else
1416  {
1417  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("Command not handled: %1")
1418  .arg(option));
1419  }
1420  FinishResponse(m_textStream, m_socket, option, tags["CSeq"], responseData);
1421 }
1422 
1424  QTcpSocket *socket,
1425  QString &cseq)
1426 {
1427  if (!stream)
1428  return;
1429  *stream << "RTSP/1.0 401 Unauthorised\r\n";
1430  *stream << "Server: AirTunes/130.14\r\n";
1431  *stream << "WWW-Authenticate: Digest realm=\"raop\", ";
1432  *stream << "nonce=\"" + m_nonce + "\"\r\n";
1433  *stream << "CSeq: " << cseq << "\r\n";
1434  *stream << "\r\n";
1435  stream->flush();
1436  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1437  QString("Finished Authentication request %2, Send: %3")
1438  .arg(cseq).arg(socket->flush()));
1439 }
1440 
1441 void MythRAOPConnection::FinishResponse(_NetStream *stream, QTcpSocket *socket,
1442  QString &option, QString &cseq, QString &responseData)
1443 {
1444  if (!stream)
1445  return;
1446  *stream << "Server: AirTunes/130.14\r\n";
1447  *stream << "CSeq: " << cseq << "\r\n";
1448  if (responseData.length())
1449  *stream << "Content-Length: " << QString::number(responseData.length()) << "\r\n\r\n" << responseData;
1450  else
1451  *stream << "\r\n";
1452  stream->flush();
1453  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("Finished %1 %2 , Send: %3")
1454  .arg(option).arg(cseq).arg(socket->flush()));
1455 }
1456 
1463 {
1464  static QMutex s_lock;
1465  QMutexLocker locker(&s_lock);
1466 
1467  if (g_rsa)
1468  return g_rsa;
1469 
1470  QString sName( "/RAOPKey.rsa" );
1471  FILE *file = fopen(GetConfDir().toUtf8() + sName.toUtf8(), "rb");
1472 
1473  if ( !file )
1474  {
1475  g_rsaLastError = tr("Failed to read key from: %1").arg(GetConfDir() + sName);
1476  g_rsa = nullptr;
1477  LOG(VB_PLAYBACK, LOG_ERR, LOC + g_rsaLastError);
1478  return nullptr;
1479  }
1480 
1481  g_rsa = PEM_read_RSAPrivateKey(file, nullptr, nullptr, nullptr);
1482  fclose(file);
1483 
1484  if (g_rsa)
1485  {
1486  g_rsaLastError = "";
1487  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1488  QString("Loaded RSA private key (%1)").arg(RSA_check_key(g_rsa)));
1489  return g_rsa;
1490  }
1491 
1492  g_rsaLastError = tr("Failed to load RSA private key.");
1493  g_rsa = nullptr;
1494  LOG(VB_PLAYBACK, LOG_ERR, LOC + g_rsaLastError);
1495  return nullptr;
1496 }
1497 
1498 RawHash MythRAOPConnection::FindTags(const QStringList &lines)
1499 {
1500  RawHash result;
1501  if (lines.isEmpty())
1502  return result;
1503 
1504  for (int i = 0; i < lines.size(); i++)
1505  {
1506  int index = lines[i].indexOf(":");
1507  if (index > 0)
1508  {
1509  result.insert(lines[i].left(index).trimmed(),
1510  lines[i].mid(index + 1).trimmed());
1511  }
1512  }
1513  return result;
1514 }
1515 
1516 QStringList MythRAOPConnection::splitLines(const QByteArray &lines)
1517 {
1518  QStringList list;
1519  QTextStream stream(lines);
1520 
1521  QString line;
1522  do
1523  {
1524  line = stream.readLine();
1525  if (!line.isNull())
1526  {
1527  list.append(line);
1528  }
1529  }
1530  while (!line.isNull());
1531 
1532  return list;
1533 }
1534 
1542 QString MythRAOPConnection::stringFromSeconds(int timeInSeconds)
1543 {
1544  int hour = timeInSeconds / 3600;
1545  int minute = (timeInSeconds - hour * 3600) / 60;
1546  int seconds = timeInSeconds - hour * 3600 - minute * 60;
1547  QString str;
1548 
1549  if (hour)
1550  {
1551  str += QString("%1:").arg(hour);
1552  }
1553  if (minute < 10)
1554  {
1555  str += "0";
1556  }
1557  str += QString("%1:").arg(minute);
1558  if (seconds < 10)
1559  {
1560  str += "0";
1561  }
1562  str += QString::number(seconds);
1563  return str;
1564 }
1565 
1572 {
1573  return (frames * 1000ULL) / m_frameRate;
1574 }
1575 
1583 QMap<QString,QString> MythRAOPConnection::decodeDMAP(const QByteArray &dmap)
1584 {
1585  QMap<QString,QString> result;
1586  int offset = 8;
1587  while (offset < dmap.size())
1588  {
1589  QString tag = dmap.mid(offset, 4);
1590  offset += 4;
1591  uint32_t length = qFromBigEndian(*(uint32_t *)(dmap.constData() + offset));
1592  offset += sizeof(uint32_t);
1593  QString content = QString::fromUtf8(dmap.mid(offset,
1594  length).constData());
1595  offset += length;
1596  result.insert(tag, content);
1597  }
1598  return result;
1599 }
1600 
1602 {
1603  DestroyDecoder();
1604 
1605  // create an ALAC decoder
1606  m_codec = avcodec_find_decoder(AV_CODEC_ID_ALAC);
1607  if (!m_codec)
1608  {
1609  LOG(VB_PLAYBACK, LOG_ERR, LOC
1610  + "Failed to create ALAC decoder- going silent...");
1611  return false;
1612  }
1613 
1614  m_codecContext = avcodec_alloc_context3(m_codec);
1615  if (m_codecContext)
1616  {
1617  auto *extradata = new unsigned char[36];
1618  memset(extradata, 0, 36);
1619  if (m_audioFormat.size() < 12)
1620  {
1621  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1622  "Creating decoder but haven't seen audio format.");
1623  }
1624  else
1625  {
1626  uint32_t fs = m_audioFormat[1]; // frame size
1627  extradata[12] = (fs >> 24) & 0xff;
1628  extradata[13] = (fs >> 16) & 0xff;
1629  extradata[14] = (fs >> 8) & 0xff;
1630  extradata[15] = fs & 0xff;
1631  extradata[16] = m_channels; // channels
1632  extradata[17] = m_audioFormat[3]; // sample size
1633  extradata[18] = m_audioFormat[4]; // rice_historymult
1634  extradata[19] = m_audioFormat[5]; // rice_initialhistory
1635  extradata[20] = m_audioFormat[6]; // rice_kmodifier
1636  }
1637  m_codecContext->extradata = extradata;
1638  m_codecContext->extradata_size = 36;
1639  m_codecContext->channels = m_channels;
1640  if (avcodec_open2(m_codecContext, m_codec, nullptr) < 0)
1641  {
1642  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1643  "Failed to open ALAC decoder - going silent...");
1644  DestroyDecoder();
1645  return false;
1646  }
1647  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "Opened ALAC decoder.");
1648  }
1649 
1650  return true;
1651 }
1652 
1654 {
1655  if (m_codecContext)
1656  {
1657  avcodec_free_context(&m_codecContext);
1658  }
1659  m_codec = nullptr;
1660 }
1661 
1663 {
1664  CloseAudioDevice();
1665 
1666  QString passthru = gCoreContext->GetBoolSetting("PassThruDeviceOverride", false)
1667  ? gCoreContext->GetSetting("PassThruOutputDevice") : QString();
1668  QString device = gCoreContext->GetSetting("AudioOutputDevice");
1669 
1670  m_audio = AudioOutput::OpenAudio(device, passthru, FORMAT_S16, m_channels,
1671  AV_CODEC_ID_NONE, m_frameRate, AUDIOOUTPUT_MUSIC,
1672  m_allowVolumeControl, false);
1673  if (!m_audio)
1674  {
1675  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1676  "Failed to open audio device. Going silent...");
1677  CloseAudioDevice();
1678  StartAudioTimer();
1679  return false;
1680  }
1681 
1682  QString error = m_audio->GetError();
1683  if (!error.isEmpty())
1684  {
1685  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1686  QString("Audio not initialised. Message was '%1'")
1687  .arg(error));
1688  CloseAudioDevice();
1689  StartAudioTimer();
1690  return false;
1691  }
1692 
1693  StopAudioTimer();
1694  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "Opened audio device.");
1695  return true;
1696 }
1697 
1699 {
1700  delete m_audio;
1701  m_audio = nullptr;
1702 }
1703 
1705 {
1706  if (m_audioTimer)
1707  return;
1708 
1709  m_audioTimer = new QTimer();
1710  connect(m_audioTimer, SIGNAL(timeout()), this, SLOT(audioRetry()));
1711  m_audioTimer->start(5000);
1712 }
1713 
1715 {
1716  if (m_audioTimer)
1717  {
1718  m_audioTimer->stop();
1719  }
1720  delete m_audioTimer;
1721  m_audioTimer = nullptr;
1722 }
1723 
1729 {
1730  if (!m_audio)
1731  return 0;
1732 
1733  auto *samples = (int16_t *)av_mallocz(AudioOutput::kMaxSizeBuffer);
1734  int frames = AUDIOCARD_BUFFER * m_frameRate / 1000;
1735  m_audio->AddData((char *)samples,
1736  frames * (m_sampleSize>>3) * m_channels,
1737  0,
1738  frames);
1739  av_free(samples);
1740  usleep(AUDIOCARD_BUFFER * 1000);
1741  uint64_t audiots = m_audio->GetAudiotime();
1742  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("AudioCardLatency: ts=%1ms")
1743  .arg(audiots));
1744  return AUDIOCARD_BUFFER - (int64_t)audiots;
1745 }
1746 
1747 void MythRAOPConnection::newEventClient(QTcpSocket *client)
1748 {
1749  LOG(VB_PLAYBACK, LOG_INFO, LOC +
1750  QString("New connection from %1:%2 for RAOP events server.")
1751  .arg(client->peerAddress().toString()).arg(client->peerPort()));
1752 
1753  m_eventClients.append(client);
1754  connect(client, SIGNAL(disconnected()), this, SLOT(deleteEventClient()));
1755 }
1756 
1758 {
1759  auto *client = dynamic_cast<QTcpSocket *>(sender());
1760  QString label;
1761 
1762  if (client != nullptr)
1763  label = QString("%1:%2").arg(client->peerAddress().toString()).arg(client->peerPort());
1764  else
1765  label = QString("unknown");
1766 
1767  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1768  QString("%1 disconnected from RAOP events server.").arg(label));
1769 }
1770 
1772 {
1773  QImage image = m_artwork.isEmpty() ? QImage() : QImage::fromData(m_artwork);
1774  int duration =
1775  lroundf(static_cast<float>(m_progressEnd-m_progressStart) / m_frameRate);
1776  int position =
1778 
1779  MythNotification *n = nullptr;
1780 
1781  if (!update || !m_firstSend)
1782  {
1784  image, m_dmap, duration, position);
1785  }
1786  else
1787  {
1789  duration, position);
1790  }
1791  n->SetId(m_id);
1792  n->SetParent(this);
1793  n->SetDuration(5);
1794  n->SetFullScreen(gCoreContext->GetBoolSetting("AirPlayFullScreen"));
1796  m_firstSend = true;
1797  delete n;
1798 }
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:77
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:194
#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:128
#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
AVCodecContext * m_codecContext
ServerPool * m_eventServer
#define AUDIO_RESEND
#define MAX_PACKET_SIZE
void emitTVPlaybackStopped(void)
virtual void Pause(bool paused)=0
int tryListeningPort(int baseport, int range=1)
tryListeningPort
Definition: serverpool.cpp:690
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.
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...
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
unsigned short uint16_t
Definition: iso6937tables.h:1
PictureAttribute next(PictureAttributeSupported Supported, PictureAttribute Attribute)
bool GetBoolSetting(const QString &key, bool defaultval=false)
qint64 writeDatagram(const char *data, qint64 size, const QHostAddress &addr, quint16 port)
Definition: serverpool.cpp:597
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:369
QList< QTcpSocket * > m_eventClients
int tryBindingPort(int baseport, int range=1)
tryBindingPort
Definition: serverpool.cpp:723
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)
#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:145
#define AUDIOCARD_BUFFER
MythNotificationCenter * GetNotificationCenter(void)
virtual int64_t GetAudioBufferedTime(void)
report amount of audio buffered in milliseconds.
Definition: audiooutput.h:141
void newEventClient(QTcpSocket *client)
QList< int > m_audioFormat