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