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