00001 #include <QTimer>
00002 #include <QTcpSocket>
00003 #include <QtEndian>
00004 #include <QTextStream>
00005
00006 #include "mythlogging.h"
00007 #include "mythcorecontext.h"
00008 #include "mythdirs.h"
00009 #include "serverpool.h"
00010
00011 #include "audiooutput.h"
00012
00013 #include "mythraopdevice.h"
00014 #include "mythraopconnection.h"
00015
00016 #define LOC QString("RAOP Conn: ")
00017 #define MAX_PACKET_SIZE 2048
00018
00019 RSA* MythRAOPConnection::g_rsa = NULL;
00020
00021
00022 #define TIMING_REQUEST 0x52
00023 #define TIMING_RESPONSE 0x53
00024 #define SYNC 0x54
00025 #define FIRSTSYNC (0x54 | 0x80)
00026 #define RANGE_RESEND 0x55
00027 #define AUDIO_RESEND 0x56
00028 #define AUDIO_DATA 0x60
00029 #define FIRSTAUDIO_DATA (0x60 | 0x80)
00030
00031
00032
00033 #define AUDIOCARD_BUFFER 800
00034
00035
00036
00037 #define AUDIO_BUFFER 100
00038
00039 class NetStream : public QTextStream
00040 {
00041 public:
00042 NetStream(QIODevice *device) : QTextStream(device)
00043 {
00044 };
00045 NetStream &operator<<(const QString &str)
00046 {
00047 LOG(VB_GENERAL, LOG_DEBUG,
00048 LOC + QString("Sending(%1): ").arg(str.length()) + str);
00049 QTextStream *q = this;
00050 *q << str;
00051 return *this;
00052 };
00053 };
00054
00055 MythRAOPConnection::MythRAOPConnection(QObject *parent, QTcpSocket *socket,
00056 QByteArray id, int port)
00057 : QObject(parent), m_watchdogTimer(NULL), m_socket(socket),
00058 m_textStream(NULL), m_hardwareId(id),
00059 m_incomingHeaders(), m_incomingContent(), m_incomingPartial(false),
00060 m_incomingSize(0),
00061 m_dataSocket(NULL), m_dataPort(port),
00062 m_clientControlSocket(NULL), m_clientControlPort(0),
00063 m_clientTimingSocket(NULL), m_clientTimingPort(0),
00064 m_audio(NULL), m_codec(NULL), m_codeccontext(NULL),
00065 m_channels(2), m_sampleSize(16), m_frameRate(44100),
00066 m_framesPerPacket(352),m_dequeueAudioTimer(NULL),
00067 m_queueLength(0), m_streamingStarted(false),
00068 m_allowVolumeControl(true),
00069
00070 m_seqNum(0),
00071 m_lastSequence(0), m_lastTimestamp(0),
00072 m_currentTimestamp(0), m_nextSequence(0), m_nextTimestamp(0),
00073 m_bufferLength(0), m_timeLastSync(0),
00074 m_cardLatency(0), m_adjustedLatency(0), m_audioStarted(false),
00075
00076 m_masterTimeStamp(0), m_deviceTimeStamp(0), m_networkLatency(0),
00077 m_clockSkew(0),
00078 m_audioTimer(NULL),
00079 m_progressStart(0), m_progressCurrent(0), m_progressEnd(0)
00080 {
00081 }
00082
00083 MythRAOPConnection::~MythRAOPConnection()
00084 {
00085
00086 StopAudioTimer();
00087
00088
00089 if (m_watchdogTimer)
00090 {
00091 m_watchdogTimer->stop();
00092 delete m_watchdogTimer;
00093 }
00094
00095 if (m_dequeueAudioTimer)
00096 {
00097 m_dequeueAudioTimer->stop();
00098 delete m_dequeueAudioTimer;
00099 }
00100
00101
00102 if (m_socket)
00103 {
00104 m_socket->close();
00105 m_socket->deleteLater();
00106 }
00107
00108
00109 if (m_dataSocket)
00110 {
00111 m_dataSocket->disconnect();
00112 m_dataSocket->close();
00113 m_dataSocket->deleteLater();
00114 }
00115
00116
00117 if (m_clientControlSocket)
00118 {
00119 m_clientControlSocket->disconnect();
00120 m_clientControlSocket->close();
00121 m_clientControlSocket->deleteLater();
00122 }
00123
00124
00125 DestroyDecoder();
00126
00127
00128 ResetAudio();
00129
00130
00131 CloseAudioDevice();
00132 }
00133
00134 bool MythRAOPConnection::Init(void)
00135 {
00136
00137 m_textStream = new NetStream(m_socket);
00138 m_textStream->setCodec("UTF-8");
00139 if (!connect(m_socket, SIGNAL(readyRead()), this, SLOT(readClient())))
00140 {
00141 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to connect client socket signal.");
00142 return false;
00143 }
00144
00145
00146 m_dataSocket = new ServerPool();
00147 if (!connect(m_dataSocket, SIGNAL(newDatagram(QByteArray, QHostAddress, quint16)),
00148 this, SLOT(udpDataReady(QByteArray, QHostAddress, quint16))))
00149 {
00150 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to connect data socket signal.");
00151 return false;
00152 }
00153
00154
00155 m_dataPort = m_dataSocket->tryBindingPort(m_dataPort, RAOP_PORT_RANGE);
00156 if (m_dataPort < 0)
00157 {
00158 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to bind to a port for data.");
00159 return false;
00160 }
00161
00162 LOG(VB_GENERAL, LOG_INFO, LOC +
00163 QString("Bound to port %1 for incoming data").arg(m_dataPort));
00164
00165
00166 if (!LoadKey())
00167 return false;
00168
00169
00170 m_allowVolumeControl = gCoreContext->GetNumSetting("MythControlsVolume", 1);
00171
00172
00173 m_watchdogTimer = new QTimer();
00174 connect(m_watchdogTimer, SIGNAL(timeout()), this, SLOT(timeout()));
00175 m_watchdogTimer->start(10000);
00176
00177 m_dequeueAudioTimer = new QTimer();
00178 connect(m_dequeueAudioTimer, SIGNAL(timeout()), this, SLOT(ProcessAudio()));
00179
00180 return true;
00181 }
00182
00187 void MythRAOPConnection::udpDataReady(QByteArray buf, QHostAddress peer,
00188 quint16 port)
00189 {
00190
00191 if (m_watchdogTimer)
00192 m_watchdogTimer->start(10000);
00193
00194 if (!m_audio || !m_codec || !m_codeccontext)
00195 return;
00196
00197 uint8_t type;
00198 uint16_t seq;
00199 uint64_t timestamp;
00200
00201 if (!GetPacketType(buf, type, seq, timestamp))
00202 {
00203 LOG(VB_GENERAL, LOG_DEBUG, LOC +
00204 QString("Packet doesn't start with valid Rtp Header (0x%1)")
00205 .arg((uint8_t)buf[0], 0, 16));
00206 return;
00207 }
00208
00209 switch (type)
00210 {
00211 case SYNC:
00212 case FIRSTSYNC:
00213 ProcessSync(buf);
00214 ProcessAudio();
00215 return;
00216
00217 case FIRSTAUDIO_DATA:
00218 m_nextSequence = seq;
00219 m_nextTimestamp = timestamp;
00220
00221
00222
00223 m_streamingStarted = true;
00224 break;
00225
00226 case AUDIO_DATA:
00227 case AUDIO_RESEND:
00228 break;
00229
00230 case TIMING_RESPONSE:
00231 ProcessTimeResponse(buf);
00232 return;
00233
00234 default:
00235 LOG(VB_GENERAL, LOG_DEBUG, LOC +
00236 QString("Packet type (0x%1) not handled")
00237 .arg(type, 0, 16));
00238 return;
00239 }
00240
00241 timestamp = framesToMs(timestamp);
00242 if (timestamp < m_currentTimestamp)
00243 {
00244 LOG(VB_GENERAL, LOG_DEBUG, LOC +
00245 QString("Received packet %1 too late, ignoring")
00246 .arg(seq));
00247 return;
00248 }
00249
00250 if (type == AUDIO_DATA || type == FIRSTAUDIO_DATA)
00251 {
00252 if (m_streamingStarted && seq != m_nextSequence)
00253 SendResendRequest(timestamp, m_nextSequence, seq);
00254
00255 m_nextSequence = seq + 1;
00256 m_nextTimestamp = timestamp;
00257 m_streamingStarted = true;
00258 }
00259
00260 if (!m_streamingStarted)
00261 return;
00262
00263
00264 if (type == AUDIO_RESEND)
00265 {
00266 if (m_resends.contains(seq))
00267 {
00268 LOG(VB_GENERAL, LOG_DEBUG, LOC +
00269 QString("Received required resend %1 (with ts:%2 last:%3)")
00270 .arg(seq).arg(timestamp).arg(m_nextSequence));
00271 m_resends.remove(seq);
00272 }
00273 else
00274 LOG(VB_GENERAL, LOG_WARNING, LOC +
00275 QString("Received unexpected resent packet %1")
00276 .arg(seq));
00277 }
00278
00279
00280
00281 QList<AudioData> *decoded = new QList<AudioData>();
00282 int numframes = decodeAudioPacket(type, &buf, decoded);
00283 if (numframes < 0)
00284 {
00285
00286 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Error decoding audio"));
00287 SendResendRequest(timestamp, seq, seq+1);
00288 return;
00289 }
00290 AudioPacket frames;
00291 frames.seq = seq;
00292 frames.data = decoded;
00293 m_audioQueue.insert(timestamp, frames);
00294 ProcessAudio();
00295 }
00296
00297 void MythRAOPConnection::ProcessSync(const QByteArray &buf)
00298 {
00299 bool first = (uint8_t)buf[0] == 0x90;
00300 const char *req = buf.constData();
00301 uint64_t current_ts = qFromBigEndian(*(uint32_t*)(req + 4));
00302 uint64_t next_ts = qFromBigEndian(*(uint32_t*)(req + 16));
00303
00304 uint64_t current = framesToMs(current_ts);
00305 uint64_t next = framesToMs(next_ts);
00306
00307 m_currentTimestamp = current;
00308 m_nextTimestamp = next;
00309 m_bufferLength = m_nextTimestamp - m_currentTimestamp;
00310
00311 if (first)
00312 {
00313 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Receiving first SYNC packet"));
00314 }
00315 else
00316 {
00317 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Receiving SYNC packet"));
00318 }
00319
00320 timeval t; gettimeofday(&t, NULL);
00321 m_timeLastSync = t.tv_sec * 1000 + t.tv_usec / 1000;
00322
00323 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("SYNC: cur:%1 next:%2 time:%3")
00324 .arg(m_currentTimestamp).arg(m_nextTimestamp).arg(m_timeLastSync));
00325
00326 uint64_t delay = framesToMs(m_audioQueue.size() * m_framesPerPacket);
00327 delay += m_networkLatency;
00328
00329
00330 if (first)
00331 {
00332 m_cardLatency = AudioCardLatency();
00333
00334
00335 LOG(VB_GENERAL, LOG_DEBUG, LOC +
00336 QString("Audio hardware latency: %1ms").arg(m_cardLatency));
00337 }
00338
00339 uint64_t audiots = m_audio->GetAudiotime();
00340 if (m_audioStarted)
00341 {
00342 m_adjustedLatency = (int64_t)audiots - (int64_t)m_currentTimestamp;
00343 }
00344 if (m_adjustedLatency > (int64_t)m_bufferLength)
00345 {
00346
00347
00348 m_audioStarted = false;
00349 m_adjustedLatency = 0;
00350 }
00351
00352 delay += m_audio->GetAudioBufferedTime();
00353 delay += m_adjustedLatency;
00354
00355
00356 ExpireResendRequests(m_currentTimestamp);
00357 int res = ExpireAudio(m_currentTimestamp);
00358 if (res > 0)
00359 {
00360 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Drop %1 packets").arg(res));
00361 }
00362
00363 LOG(VB_GENERAL, LOG_DEBUG, LOC +
00364 QString("Queue=%1 buffer=%2ms ideal=%3ms diffts:%4ms")
00365 .arg(m_audioQueue.size())
00366 .arg(delay)
00367 .arg(m_bufferLength)
00368 .arg(m_adjustedLatency));
00369 }
00370
00375 void MythRAOPConnection::SendResendRequest(uint64_t timestamp,
00376 uint16_t expected, uint16_t got)
00377 {
00378 if (!m_clientControlSocket)
00379 return;
00380
00381 int16_t missed = (got < expected) ?
00382 (int16_t)(((int32_t)got + UINT16_MAX + 1) - expected) :
00383 got - expected;
00384
00385 LOG(VB_GENERAL, LOG_INFO, LOC +
00386 QString("Missed %1 packet(s): expected %2 got %3 ts:%4")
00387 .arg(missed).arg(expected).arg(got).arg(timestamp));
00388
00389 char req[8];
00390 req[0] = 0x80;
00391 req[1] = RANGE_RESEND | 0x80;
00392 *(uint16_t *)(req + 2) = qToBigEndian(m_seqNum++);
00393 *(uint16_t *)(req + 4) = qToBigEndian(expected);
00394 *(uint16_t *)(req + 6) = qToBigEndian(missed);
00395
00396 if (m_clientControlSocket->writeDatagram(req, sizeof(req),
00397 m_peerAddress, m_clientControlPort)
00398 == sizeof(req))
00399 {
00400 for (uint16_t count = 0; count < missed; count++)
00401 {
00402 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Sent resend for %1")
00403 .arg(expected + count));
00404 m_resends.insert(expected + count, timestamp);
00405 }
00406 }
00407 else
00408 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to send resend request.");
00409 }
00410
00416 void MythRAOPConnection::ExpireResendRequests(uint64_t timestamp)
00417 {
00418 if (!m_resends.size())
00419 return;
00420
00421 QMutableMapIterator<uint16_t,uint64_t> it(m_resends);
00422 while (it.hasNext())
00423 {
00424 it.next();
00425 if (it.value() < timestamp && m_streamingStarted)
00426 {
00427 LOG(VB_GENERAL, LOG_WARNING, LOC +
00428 QString("Never received resend packet %1").arg(it.key()));
00429 m_resends.remove(it.key());
00430 }
00431 }
00432 }
00433
00438 void MythRAOPConnection::SendTimeRequest(void)
00439 {
00440 if (!m_clientControlSocket)
00441 return;
00442
00443 timeval t;
00444 gettimeofday(&t, NULL);
00445
00446 char req[32];
00447 req[0] = 0x80;
00448 req[1] = TIMING_REQUEST | 0x80;
00449
00450
00451 req[2] = 0x00;
00452 req[3] = 0x07;
00453 *(uint32_t *)(req + 4) = (uint32_t)0;
00454 *(uint64_t *)(req + 8) = (uint64_t)0;
00455 *(uint64_t *)(req + 16) = (uint64_t)0;
00456 *(uint32_t *)(req + 24) = qToBigEndian((uint32_t)t.tv_sec);
00457 *(uint32_t *)(req + 28) = qToBigEndian((uint32_t)t.tv_usec);
00458
00459 if (m_clientTimingSocket->writeDatagram(req, sizeof(req), m_peerAddress, m_clientTimingPort) != sizeof(req))
00460 {
00461 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to send resend time request.");
00462 return;
00463 }
00464 LOG(VB_GENERAL, LOG_DEBUG, LOC +
00465 QString("Requesting master time (Local %1.%2)")
00466 .arg(t.tv_sec).arg(t.tv_usec));
00467 }
00468
00475 void MythRAOPConnection::ProcessTimeResponse(const QByteArray &buf)
00476 {
00477 timeval t1, t2;
00478 const char *req = buf.constData();
00479
00480 t1.tv_sec = qFromBigEndian(*(uint32_t*)(req + 8));
00481 t1.tv_usec = qFromBigEndian(*(uint32_t*)(req + 12));
00482
00483 gettimeofday(&t2, NULL);
00484 uint64_t time1, time2;
00485 time1 = t1.tv_sec * 1000 + t1.tv_usec / 1000;
00486 time2 = t2.tv_sec * 1000 + t2.tv_usec / 1000;
00487 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Read back time (Local %1.%2)")
00488 .arg(t1.tv_sec).arg(t1.tv_usec));
00489
00490
00491 m_networkLatency = (time2 - time1) / 2;
00492
00493
00494
00495 uint32_t sec = qFromBigEndian(*(uint32_t*)(req + 24));
00496 uint32_t ticks = qFromBigEndian(*(uint32_t*)(req + 28));
00497
00498 int64_t master = NTPToLocal(sec, ticks);
00499 m_clockSkew = master - time2;
00500 }
00501
00502 uint64_t MythRAOPConnection::NTPToLocal(uint32_t sec, uint32_t ticks)
00503 {
00504 return (int64_t)sec * 1000LL + (((int64_t)ticks * 1000LL) >> 32);
00505 }
00506
00507 bool MythRAOPConnection::GetPacketType(const QByteArray &buf, uint8_t &type,
00508 uint16_t &seq, uint64_t ×tamp)
00509 {
00510
00511 if ((uint8_t)buf[0] != 0x80 && (uint8_t)buf[0] != 0x90)
00512 {
00513 return false;
00514 }
00515
00516 type = (char)buf[1];
00517
00518 if ((uint8_t)buf[0] == 0x90 && type == FIRSTSYNC)
00519 {
00520 return true;
00521 }
00522 if (type != FIRSTAUDIO_DATA)
00523 {
00524 type &= ~0x80;
00525 }
00526
00527 if (type != AUDIO_DATA && type != FIRSTAUDIO_DATA && type != AUDIO_RESEND)
00528 return true;
00529
00530 const char *ptr = buf.constData();
00531 if (type == AUDIO_RESEND)
00532 {
00533 ptr += 4;
00534 }
00535 seq = qFromBigEndian(*(uint16_t *)(ptr + 2));
00536 timestamp = qFromBigEndian(*(uint32_t*)(ptr + 4));
00537 return true;
00538 }
00539
00540
00541
00542 uint32_t MythRAOPConnection::decodeAudioPacket(uint8_t type,
00543 const QByteArray *buf,
00544 QList<AudioData> *dest)
00545 {
00546 const char *data_in = buf->constData();
00547 int len = buf->size();
00548 if (type == AUDIO_RESEND)
00549 {
00550 data_in += 4;
00551 len -= 4;
00552 }
00553 data_in += 12;
00554 len -= 12;
00555 if (len < 16)
00556 return -1;
00557
00558 int aeslen = len & ~0xf;
00559 unsigned char iv[16];
00560 unsigned char decrypted_data[MAX_PACKET_SIZE];
00561 memcpy(iv, m_AESIV.constData(), sizeof(iv));
00562 AES_cbc_encrypt((const unsigned char*)data_in,
00563 decrypted_data, aeslen,
00564 &m_aesKey, iv, AES_DECRYPT);
00565 memcpy(decrypted_data + aeslen, data_in + aeslen, len - aeslen);
00566
00567 AVPacket tmp_pkt;
00568 AVCodecContext *ctx = m_codeccontext;
00569
00570 av_init_packet(&tmp_pkt);
00571 tmp_pkt.data = decrypted_data;
00572 tmp_pkt.size = len;
00573
00574 uint32_t frames_added = 0;
00575 while (tmp_pkt.size > 0)
00576 {
00577 uint8_t *samples = (uint8_t *)av_mallocz(AVCODEC_MAX_AUDIO_FRAME_SIZE);
00578 AVFrame frame;
00579 int got_frame = 0;
00580
00581 int ret = avcodec_decode_audio4(ctx, &frame, &got_frame, &tmp_pkt);
00582
00583 if (ret < 0)
00584 {
00585 av_free(samples);
00586 return -1;
00587 }
00588
00589 if (got_frame)
00590 {
00591
00592 int data_size = av_samples_get_buffer_size(NULL, ctx->channels,
00593 frame.nb_samples,
00594 ctx->sample_fmt, 1);
00595 memcpy(samples, frame.extended_data[0], data_size);
00596
00597 frames_added += frame.nb_samples;
00598 AudioData block;
00599 block.data = samples;
00600 block.length = data_size;
00601 block.frames = frame.nb_samples;
00602 dest->append(block);
00603 }
00604 tmp_pkt.data += ret;
00605 tmp_pkt.size -= ret;
00606 }
00607 return frames_added;
00608 }
00609
00610 void MythRAOPConnection::ProcessAudio()
00611 {
00612 if (!m_streamingStarted || !m_audio)
00613 return;
00614
00615 if (m_audio->IsPaused())
00616 {
00617
00618
00619 m_audio->Pause(false);
00620 }
00621 timeval t; gettimeofday(&t, NULL);
00622 uint64_t dtime = (t.tv_sec * 1000 + t.tv_usec / 1000) - m_timeLastSync;
00623 uint64_t rtp = dtime + m_currentTimestamp + m_networkLatency;
00624 uint64_t buffered = m_audioStarted ? m_audio->GetAudioBufferedTime() : 0;
00625
00626
00627
00628 if (buffered > AUDIOCARD_BUFFER)
00629 return;
00630
00631
00632
00633 uint64_t queue = framesToMs(m_audioQueue.size() * m_framesPerPacket);
00634 if (queue < m_bufferLength / 3)
00635 return;
00636
00637 rtp += buffered;
00638 rtp += m_cardLatency;
00639
00640
00641 int max_packets = ((AUDIOCARD_BUFFER - buffered)
00642 * m_frameRate / 1000) / m_framesPerPacket;
00643 int i = 0;
00644 uint64_t timestamp = 0;
00645
00646 QMapIterator<uint64_t,AudioPacket> packet_it(m_audioQueue);
00647 while (packet_it.hasNext() && i <= max_packets)
00648 {
00649 packet_it.next();
00650
00651 timestamp = packet_it.key();
00652 if (timestamp < rtp)
00653 {
00654 if (!m_audioStarted)
00655 {
00656 m_audio->Reset();
00657 }
00658 AudioPacket frames = packet_it.value();
00659
00660 if (m_lastSequence != frames.seq)
00661 {
00662 LOG(VB_GENERAL, LOG_ERR, LOC +
00663 QString("Audio discontinuity seen. Played %1 (%3) expected %2")
00664 .arg(frames.seq).arg(m_lastSequence).arg(timestamp));
00665 m_lastSequence = frames.seq;
00666 }
00667 m_lastSequence++;
00668
00669 QList<AudioData>::iterator it = frames.data->begin();
00670 for (; it != frames.data->end(); ++it)
00671 {
00672 AudioData *data = &(*it);
00673 m_audio->AddData((char *)data->data, data->length,
00674 timestamp, data->frames);
00675 timestamp += m_audio->LengthLastData();
00676 }
00677 i++;
00678 m_audioStarted = true;
00679 }
00680 else
00681 break;
00682 }
00683
00684 ExpireAudio(timestamp);
00685 m_lastTimestamp = timestamp;
00686
00687
00688
00689 m_dequeueAudioTimer->start(AUDIO_BUFFER);
00690 }
00691
00692 int MythRAOPConnection::ExpireAudio(uint64_t timestamp)
00693 {
00694 int res = 0;
00695 QMutableMapIterator<uint64_t,AudioPacket> packet_it(m_audioQueue);
00696 while (packet_it.hasNext())
00697 {
00698 packet_it.next();
00699 if (packet_it.key() < timestamp)
00700 {
00701 AudioPacket frames = packet_it.value();
00702 if (frames.data)
00703 {
00704 QList<AudioData>::iterator it = frames.data->begin();
00705 for (; it != frames.data->end(); ++it)
00706 {
00707 av_free(it->data);
00708 }
00709 delete frames.data;
00710 }
00711 m_audioQueue.remove(packet_it.key());
00712 res++;
00713 }
00714 }
00715 return res;
00716 }
00717
00718 void MythRAOPConnection::ResetAudio(void)
00719 {
00720 if (m_audio)
00721 {
00722 m_audio->Reset();
00723 }
00724 ExpireAudio(UINT64_MAX);
00725 ExpireResendRequests(UINT64_MAX);
00726 m_audioStarted = false;
00727 }
00728
00729 void MythRAOPConnection::timeout(void)
00730 {
00731 LOG(VB_GENERAL, LOG_INFO, LOC + "Closing connection after inactivity.");
00732 m_socket->disconnectFromHost();
00733 }
00734
00735 void MythRAOPConnection::audioRetry(void)
00736 {
00737 if (!m_audio)
00738 {
00739 MythRAOPDevice* p = (MythRAOPDevice*)parent();
00740 if (p && p->NextInAudioQueue(this) && OpenAudioDevice())
00741 {
00742 CreateDecoder();
00743 }
00744 }
00745
00746 if (m_audio && m_codec && m_codeccontext)
00747 {
00748 StopAudioTimer();
00749 }
00750 }
00751
00756 void MythRAOPConnection::readClient(void)
00757 {
00758 QTcpSocket *socket = (QTcpSocket *)sender();
00759 if (!socket)
00760 return;
00761
00762 QByteArray data = socket->readAll();
00763 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("readClient(%1): ")
00764 .arg(data.size()) + data.constData());
00765
00766
00767 if (!m_incomingPartial)
00768 {
00769 m_incomingHeaders.clear();
00770 m_incomingContent.clear();
00771 m_incomingSize = 0;
00772
00773 QTextStream stream(data);
00774 QString line;
00775 do
00776 {
00777 line = stream.readLine();
00778 if (line.size() == 0)
00779 break;
00780 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Header = %1").arg(line));
00781 m_incomingHeaders.append(line);
00782 if (line.contains("Content-Length:"))
00783 {
00784 m_incomingSize = line.mid(line.indexOf(" ") + 1).toInt();
00785 }
00786 }
00787 while (!line.isNull());
00788
00789 if (m_incomingHeaders.size() == 0)
00790 return;
00791
00792 if (!stream.atEnd())
00793 {
00794 int pos = stream.pos();
00795 if (pos > 0)
00796 {
00797 m_incomingContent.append(data.mid(pos));
00798 }
00799 }
00800 }
00801 else
00802 {
00803 m_incomingContent.append(data);
00804 }
00805
00806
00807
00808 if (m_incomingContent.size() < m_incomingSize)
00809 {
00810 m_incomingPartial = true;
00811 return;
00812 }
00813 else
00814 {
00815 m_incomingPartial = false;
00816 }
00817 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Content(%1) = %2")
00818 .arg(m_incomingContent.size()).arg(m_incomingContent.constData()));
00819
00820 ProcessRequest(m_incomingHeaders, m_incomingContent);
00821 }
00822
00823 void MythRAOPConnection::ProcessRequest(const QStringList &header,
00824 const QByteArray &content)
00825 {
00826 if (header.isEmpty())
00827 return;
00828
00829 RawHash tags = FindTags(header);
00830
00831 if (!tags.contains("CSeq"))
00832 {
00833 LOG(VB_GENERAL, LOG_ERR, LOC + "ProcessRequest: Didn't find CSeq");
00834 return;
00835 }
00836
00837 *m_textStream << "RTSP/1.0 200 OK\r\n";
00838
00839 QString option = header[0].left(header[0].indexOf(" "));
00840
00841
00842 bool gotRTP = false;
00843 uint16_t RTPseq;
00844 uint64_t RTPtimestamp;
00845 if (tags.contains("RTP-Info"))
00846 {
00847 gotRTP = true;
00848 QString data = tags["RTP-Info"];
00849 QStringList items = data.split(";");
00850 foreach (QString item, items)
00851 {
00852 if (item.startsWith("seq"))
00853 {
00854 RTPseq = item.mid(item.indexOf("=") + 1).trimmed().toUShort();
00855 }
00856 else if (item.startsWith("rtptime"))
00857 {
00858 RTPtimestamp = item.mid(item.indexOf("=") + 1).trimmed().toUInt();
00859 }
00860 }
00861 LOG(VB_GENERAL, LOG_INFO, LOC + QString("RTP-Info: seq=%1 rtptime=%2")
00862 .arg(RTPseq).arg(RTPtimestamp));
00863 }
00864
00865 if (option == "OPTIONS")
00866 {
00867 if (tags.contains("Apple-Challenge"))
00868 {
00869 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Received Apple-Challenge"));
00870
00871 *m_textStream << "Apple-Response: ";
00872 if (!LoadKey())
00873 return;
00874 int tosize = RSA_size(LoadKey());
00875 uint8_t *to = new uint8_t[tosize];
00876
00877 QByteArray challenge =
00878 QByteArray::fromBase64(tags["Apple-Challenge"].toAscii());
00879 int challenge_size = challenge.size();
00880 if (challenge_size != 16)
00881 {
00882 LOG(VB_GENERAL, LOG_ERR, LOC +
00883 QString("Decoded challenge size %1, expected 16")
00884 .arg(challenge_size));
00885 if (challenge_size > 16)
00886 challenge_size = 16;
00887 }
00888
00889 int i = 0;
00890 unsigned char from[38];
00891 memcpy(from, challenge.constData(), challenge_size);
00892 i += challenge_size;
00893 if (m_socket->localAddress().protocol() ==
00894 QAbstractSocket::IPv4Protocol)
00895 {
00896 uint32_t ip = m_socket->localAddress().toIPv4Address();
00897 ip = qToBigEndian(ip);
00898 memcpy(from + i, &ip, 4);
00899 i += 4;
00900 }
00901 else if (m_socket->localAddress().protocol() ==
00902 QAbstractSocket::IPv6Protocol)
00903 {
00904 Q_IPV6ADDR ip = m_socket->localAddress().toIPv6Address();
00905 if(memcmp(&ip,
00906 "\x00\x00\x00\x00" "\x00\x00\x00\x00" "\x00\x00\xff\xff",
00907 12) == 0)
00908 {
00909 memcpy(from + i, &ip[12], 4);
00910 i += 4;
00911 }
00912 else
00913 {
00914 memcpy(from + i, &ip, 16);
00915 i += 16;
00916 }
00917 }
00918 memcpy(from + i, m_hardwareId.constData(), RAOP_HARDWARE_ID_SIZE);
00919 i += RAOP_HARDWARE_ID_SIZE;
00920
00921 int pad = 32 - i;
00922 if (pad > 0)
00923 {
00924 memset(from + i, 0, pad);
00925 i += pad;
00926 }
00927
00928 LOG(VB_GENERAL, LOG_DEBUG, LOC +
00929 QString("Full base64 response: '%1' size %2")
00930 .arg(QByteArray((const char*)from, i).toBase64().constData())
00931 .arg(i));
00932
00933 RSA_private_encrypt(i, from, to, LoadKey(), RSA_PKCS1_PADDING);
00934
00935 QByteArray base64 = QByteArray((const char*)to, tosize).toBase64();
00936 delete[] to;
00937
00938 for (int pos = base64.size() - 1; pos > 0; pos--)
00939 {
00940 if (base64[pos] == '=')
00941 base64[pos] = ' ';
00942 else
00943 break;
00944 }
00945 LOG(VB_GENERAL, LOG_DEBUG, QString("tSize=%1 tLen=%2 tResponse=%3")
00946 .arg(tosize).arg(base64.size()).arg(base64.constData()));
00947 *m_textStream << base64.trimmed() << "\r\n";
00948 }
00949 StartResponse(m_textStream, option, tags["CSeq"]);
00950 *m_textStream << "Public: ANNOUNCE, SETUP, RECORD, PAUSE, FLUSH, "
00951 "TEARDOWN, OPTIONS, GET_PARAMETER, SET_PARAMETER, POST, GET\r\n";
00952 }
00953 else if (option == "ANNOUNCE")
00954 {
00955 QStringList lines = splitLines(content);
00956 foreach (QString line, lines)
00957 {
00958 if (line.startsWith("a=rsaaeskey:"))
00959 {
00960 QString key = line.mid(12).trimmed();
00961 QByteArray decodedkey = QByteArray::fromBase64(key.toAscii());
00962 LOG(VB_GENERAL, LOG_DEBUG, LOC +
00963 QString("RSAAESKey: %1 (decoded size %2)")
00964 .arg(key).arg(decodedkey.size()));
00965
00966 if (LoadKey())
00967 {
00968 int size = sizeof(char) * RSA_size(LoadKey());
00969 char *decryptedkey = new char[size];
00970 if (RSA_private_decrypt(decodedkey.size(),
00971 (const unsigned char*)decodedkey.constData(),
00972 (unsigned char*)decryptedkey,
00973 LoadKey(), RSA_PKCS1_OAEP_PADDING))
00974 {
00975 LOG(VB_GENERAL, LOG_DEBUG, LOC +
00976 "Successfully decrypted AES key from RSA.");
00977 AES_set_decrypt_key((const unsigned char*)decryptedkey,
00978 128, &m_aesKey);
00979 }
00980 else
00981 {
00982 LOG(VB_GENERAL, LOG_WARNING, LOC +
00983 "Failed to decrypt AES key from RSA.");
00984 }
00985 delete [] decryptedkey;
00986 }
00987
00988 }
00989 else if (line.startsWith("a=aesiv:"))
00990 {
00991 QString aesiv = line.mid(8).trimmed();
00992 m_AESIV = QByteArray::fromBase64(aesiv.toAscii());
00993 LOG(VB_GENERAL, LOG_DEBUG, LOC +
00994 QString("AESIV: %1 (decoded size %2)")
00995 .arg(aesiv).arg(m_AESIV.size()));
00996 }
00997 else if (line.startsWith("a=fmtp:"))
00998 {
00999 m_audioFormat.clear();
01000 QString format = line.mid(7).trimmed();
01001 QList<QString> fmts = format.split(' ');
01002 foreach (QString fmt, fmts)
01003 m_audioFormat.append(fmt.toInt());
01004
01005 foreach (int fmt, m_audioFormat)
01006 LOG(VB_GENERAL, LOG_DEBUG, LOC +
01007 QString("Audio parameter: %1").arg(fmt));
01008 m_framesPerPacket = m_audioFormat[1];
01009 m_sampleSize = m_audioFormat[3];
01010 m_channels = m_audioFormat[7];
01011 m_frameRate = m_audioFormat[11];
01012 }
01013 }
01014 StartResponse(m_textStream, option, tags["CSeq"]);
01015 }
01016 else if (option == "SETUP")
01017 {
01018 if (tags.contains("Transport"))
01019 {
01020 int control_port = 0;
01021 int timing_port = 0;
01022 QString data = tags["Transport"];
01023 QStringList items = data.split(";");
01024
01025 foreach (QString item, items)
01026 {
01027 if (item.startsWith("control_port"))
01028 control_port = item.mid(item.indexOf("=") + 1).trimmed().toUInt();
01029 else if (item.startsWith("timing_port"))
01030 timing_port = item.mid(item.indexOf("=") + 1).trimmed().toUInt();
01031 }
01032
01033 LOG(VB_GENERAL, LOG_INFO, LOC +
01034 QString("Negotiated setup with client %1 on port %2")
01035 .arg(m_socket->peerAddress().toString())
01036 .arg(m_socket->peerPort()));
01037 LOG(VB_GENERAL, LOG_DEBUG, LOC +
01038 QString("control port: %1 timing port: %2")
01039 .arg(control_port).arg(timing_port));
01040
01041 m_peerAddress = m_socket->peerAddress();
01042
01043 if (m_clientControlSocket)
01044 {
01045 m_clientControlSocket->disconnect();
01046 m_clientControlSocket->close();
01047 delete m_clientControlSocket;
01048 }
01049
01050 m_clientControlSocket = new ServerPool(this);
01051 int controlbind_port =
01052 m_clientControlSocket->tryBindingPort(control_port,
01053 RAOP_PORT_RANGE);
01054 if (controlbind_port < 0)
01055 {
01056 LOG(VB_GENERAL, LOG_ERR, LOC +
01057 QString("Failed to bind to client control port. "
01058 "Control of audio stream may fail"));
01059 }
01060 else
01061 {
01062 LOG(VB_GENERAL, LOG_INFO, LOC +
01063 QString("Bound to client control port %1 on port %2")
01064 .arg(control_port).arg(controlbind_port));
01065 }
01066 m_clientControlPort = control_port;
01067 connect(m_clientControlSocket,
01068 SIGNAL(newDatagram(QByteArray, QHostAddress, quint16)),
01069 this,
01070 SLOT(udpDataReady(QByteArray, QHostAddress, quint16)));
01071
01072 if (m_clientTimingSocket)
01073 {
01074 m_clientTimingSocket->disconnect();
01075 m_clientTimingSocket->close();
01076 delete m_clientTimingSocket;
01077 }
01078
01079 m_clientTimingSocket = new ServerPool(this);
01080 int timingbind_port =
01081 m_clientTimingSocket->tryBindingPort(timing_port,
01082 RAOP_PORT_RANGE);
01083 if (timingbind_port < 0)
01084 {
01085 LOG(VB_GENERAL, LOG_ERR, LOC +
01086 QString("Failed to bind to client timing port. "
01087 "Timing of audio stream will be incorrect"));
01088 }
01089 else
01090 {
01091 LOG(VB_GENERAL, LOG_INFO, LOC +
01092 QString("Bound to client timing port %1 on port %2")
01093 .arg(timing_port).arg(timingbind_port));
01094 }
01095 m_clientTimingPort = timing_port;
01096 connect(m_clientTimingSocket,
01097 SIGNAL(newDatagram(QByteArray, QHostAddress, quint16)),
01098 this,
01099 SLOT(udpDataReady(QByteArray, QHostAddress, quint16)));
01100
01101 if (OpenAudioDevice())
01102 CreateDecoder();
01103
01104
01105 QString newdata;
01106 bool first = true;
01107 foreach (QString item, items)
01108 {
01109 if (!first)
01110 {
01111 newdata += ";";
01112 }
01113 if (item.startsWith("control_port"))
01114 {
01115 newdata += "control_port=" + QString::number(controlbind_port);
01116 }
01117 else if (item.startsWith("timing_port"))
01118 {
01119 newdata += "timing_port=" + QString::number(timingbind_port);
01120 }
01121 else
01122 {
01123 newdata += item;
01124 }
01125 first = false;
01126 }
01127 if (!first)
01128 {
01129 newdata += ";";
01130 }
01131 newdata += "server_port=" + QString::number(m_dataPort);
01132
01133 StartResponse(m_textStream, option, tags["CSeq"]);
01134 *m_textStream << "Transport: " << newdata;
01135 *m_textStream << "\r\nSession: MYTHTV\r\n";
01136 }
01137 else
01138 {
01139 LOG(VB_GENERAL, LOG_ERR, LOC +
01140 "No Transport details found - Ignoring");
01141 }
01142 }
01143 else if (option == "RECORD")
01144 {
01145 if (gotRTP)
01146 {
01147 m_nextSequence = RTPseq;
01148 m_nextTimestamp = RTPtimestamp;
01149 }
01150
01151 SendTimeRequest();
01152 StartResponse(m_textStream, option, tags["CSeq"]);
01153 }
01154 else if (option == "FLUSH")
01155 {
01156 if (gotRTP)
01157 {
01158 m_nextSequence = RTPseq;
01159 m_nextTimestamp = RTPtimestamp;
01160 m_currentTimestamp = m_nextTimestamp - m_bufferLength;
01161 }
01162
01163 uint64_t timestamp = m_audioStarted && m_audio ?
01164 m_audio->GetAudiotime() : m_lastTimestamp;
01165 *m_textStream << "RTP-Info: rtptime=" << QString::number(timestamp);
01166 m_streamingStarted = false;
01167 ResetAudio();
01168 StartResponse(m_textStream, option, tags["CSeq"]);
01169 }
01170 else if (option == "SET_PARAMETER")
01171 {
01172 if (tags.contains("Content-Type"))
01173 {
01174 if (tags["Content-Type"] == "text/parameters")
01175 {
01176 QString name = content.left(content.indexOf(":"));
01177 QString param = content.mid(content.indexOf(":") + 1).trimmed();
01178
01179 LOG(VB_GENERAL, LOG_DEBUG, LOC +
01180 QString("text/parameters: name=%1 parem=%2")
01181 .arg(name).arg(param));
01182
01183 if (name == "volume" && m_allowVolumeControl && m_audio)
01184 {
01185 float volume = (param.toFloat() + 30.0f) * 100.0f / 30.0f;
01186 if (volume < 0.01f)
01187 volume = 0.0f;
01188 LOG(VB_GENERAL, LOG_INFO,
01189 LOC + QString("Setting volume to %1 (raw %3)")
01190 .arg(volume).arg(param));
01191 m_audio->SetCurrentVolume((int)volume);
01192 }
01193 else if (name == "progress")
01194 {
01195 QStringList items = param.split("/");
01196 if (items.size() == 3)
01197 {
01198 m_progressStart = items[0].toUInt();
01199 m_progressCurrent = items[1].toUInt();
01200 m_progressEnd = items[2].toUInt();
01201 }
01202 int length =
01203 (m_progressEnd-m_progressStart) / m_frameRate;
01204 int current =
01205 (m_progressCurrent-m_progressStart) / m_frameRate;
01206
01207 LOG(VB_GENERAL, LOG_INFO,
01208 LOC +QString("Progress: %1/%2")
01209 .arg(stringFromSeconds(current))
01210 .arg(stringFromSeconds(length)));
01211 }
01212 }
01213 else if(tags["Content-Type"] == "image/jpeg")
01214 {
01215
01216 m_artwork = content;
01217 }
01218 else if (tags["Content-Type"] == "application/x-dmap-tagged")
01219 {
01220
01221 QMap<QString,QString> map = decodeDMAP(content);
01222 LOG(VB_GENERAL, LOG_INFO,
01223 QString("Receiving Title:%1 Artist:%2 Album:%3 Format:%4")
01224 .arg(map["minm"]).arg(map["asar"])
01225 .arg(map["asal"]).arg(map["asfm"]));
01226 }
01227 }
01228 StartResponse(m_textStream, option, tags["CSeq"]);
01229 }
01230 else if (option == "TEARDOWN")
01231 {
01232 StartResponse(m_textStream, option, tags["CSeq"]);
01233 *m_textStream << "Connection: close\r\n";
01234 }
01235 else
01236 {
01237 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Command not handled: %1")
01238 .arg(option));
01239 StartResponse(m_textStream, option, tags["CSeq"]);
01240 }
01241 FinishResponse(m_textStream, m_socket, option, tags["CSeq"]);
01242 }
01243
01244
01245 void MythRAOPConnection::StartResponse(NetStream *stream,
01246 QString &option, QString &cseq)
01247 {
01248 if (!stream)
01249 return;
01250 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("%1 sequence %2")
01251 .arg(option).arg(cseq));
01252 *stream << "Audio-Jack-Status: connected; type=analog\r\n";
01253 *stream << "CSeq: " << cseq << "\r\n";
01254 }
01255
01256 void MythRAOPConnection::FinishResponse(NetStream *stream, QTcpSocket *socket,
01257 QString &option, QString &cseq)
01258 {
01259 *stream << "\r\n";
01260 stream->flush();
01261 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Finished %1 %2 , Send: %3")
01262 .arg(option).arg(cseq).arg(socket->flush()));
01263 }
01264
01270 RSA* MythRAOPConnection::LoadKey(void)
01271 {
01272 static QMutex lock;
01273 QMutexLocker locker(&lock);
01274
01275 if (g_rsa)
01276 return g_rsa;
01277
01278 QString sName( "/RAOPKey.rsa" );
01279 FILE * file = fopen(GetConfDir().toUtf8() + sName.toUtf8(), "rb");
01280
01281 if ( !file )
01282 {
01283 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Failed to read key from: %1")
01284 .arg(GetConfDir() + sName));
01285 g_rsa = NULL;
01286 return NULL;
01287 }
01288
01289 g_rsa = PEM_read_RSAPrivateKey(file, NULL, NULL, NULL);
01290 fclose(file);
01291
01292 if (g_rsa)
01293 {
01294 LOG(VB_GENERAL, LOG_DEBUG, LOC +
01295 QString("Loaded RSA private key (%1)").arg(RSA_check_key(g_rsa)));
01296 return g_rsa;
01297 }
01298
01299 g_rsa = NULL;
01300 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to load RSA private key.");
01301 return NULL;
01302 }
01303
01304 RawHash MythRAOPConnection::FindTags(const QStringList &lines)
01305 {
01306 RawHash result;
01307 if (lines.isEmpty())
01308 return result;
01309
01310 for (int i = 0; i < lines.size(); i++)
01311 {
01312 int index = lines[i].indexOf(":");
01313 if (index > 0)
01314 {
01315 result.insert(lines[i].left(index).trimmed(),
01316 lines[i].mid(index + 1).trimmed());
01317 }
01318 }
01319 return result;
01320 }
01321
01322 QStringList MythRAOPConnection::splitLines(const QByteArray &lines)
01323 {
01324 QStringList list;
01325 QTextStream stream(lines);
01326
01327 QString line;
01328 do
01329 {
01330 line = stream.readLine();
01331 if (!line.isNull())
01332 {
01333 list.append(line);
01334 }
01335 }
01336 while (!line.isNull());
01337
01338 return list;
01339 }
01340
01348 QString MythRAOPConnection::stringFromSeconds(int time)
01349 {
01350 int hour = time / 3600;
01351 int minute = (time - hour * 3600) / 60;
01352 int seconds = time - hour * 3600 - minute * 60;
01353 QString str;
01354
01355 if (hour)
01356 {
01357 str += QString("%1:").arg(hour);
01358 }
01359 if (minute < 10)
01360 {
01361 str += "0";
01362 }
01363 str += QString("%1:").arg(minute);
01364 if (seconds < 10)
01365 {
01366 str += "0";
01367 }
01368 str += QString::number(seconds);
01369 return str;
01370 }
01371
01377 uint64_t MythRAOPConnection::framesToMs(uint64_t frames)
01378 {
01379 return (frames * 1000ULL) / m_frameRate;
01380 }
01381
01389 QMap<QString,QString> MythRAOPConnection::decodeDMAP(const QByteArray &dmap)
01390 {
01391 QMap<QString,QString> result;
01392 int offset = 8;
01393 while (offset < dmap.size())
01394 {
01395 QString tag = dmap.mid(offset, 4);
01396 offset += 4;
01397 uint32_t length = qFromBigEndian(*(uint32_t *)(dmap.constData() + offset));
01398 offset += sizeof(uint32_t);
01399 QString content = QString::fromUtf8(dmap.mid(offset,
01400 length).constData());
01401 offset += length;
01402 result.insert(tag, content);
01403 }
01404 return result;
01405 }
01406
01407 bool MythRAOPConnection::CreateDecoder(void)
01408 {
01409 DestroyDecoder();
01410
01411
01412 avcodeclock->lock();
01413 av_register_all();
01414 avcodeclock->unlock();
01415
01416 m_codec = avcodec_find_decoder(CODEC_ID_ALAC);
01417 if (!m_codec)
01418 {
01419 LOG(VB_GENERAL, LOG_ERR, LOC
01420 + "Failed to create ALAC decoder- going silent...");
01421 return false;
01422 }
01423
01424 m_codeccontext = avcodec_alloc_context3(m_codec);
01425 if (m_codeccontext)
01426 {
01427 unsigned char* extradata = new unsigned char[36];
01428 memset(extradata, 0, 36);
01429 if (m_audioFormat.size() < 12)
01430 {
01431 LOG(VB_GENERAL, LOG_ERR, LOC +
01432 "Creating decoder but haven't seen audio format.");
01433 }
01434 else
01435 {
01436 uint32_t fs = m_audioFormat[1];
01437 extradata[12] = (fs >> 24) & 0xff;
01438 extradata[13] = (fs >> 16) & 0xff;
01439 extradata[14] = (fs >> 8) & 0xff;
01440 extradata[15] = fs & 0xff;
01441 extradata[16] = m_channels;
01442 extradata[17] = m_audioFormat[3];
01443 extradata[18] = m_audioFormat[4];
01444 extradata[19] = m_audioFormat[5];
01445 extradata[20] = m_audioFormat[6];
01446 }
01447 m_codeccontext->extradata = extradata;
01448 m_codeccontext->extradata_size = 36;
01449 m_codeccontext->channels = m_channels;
01450 if (avcodec_open2(m_codeccontext, m_codec, NULL) < 0)
01451 {
01452 LOG(VB_GENERAL, LOG_ERR, LOC +
01453 "Failed to open ALAC decoder - going silent...");
01454 DestroyDecoder();
01455 return false;
01456 }
01457 LOG(VB_GENERAL, LOG_DEBUG, LOC + "Opened ALAC decoder.");
01458 }
01459
01460 return true;
01461 }
01462
01463 void MythRAOPConnection::DestroyDecoder(void)
01464 {
01465 if (m_codeccontext)
01466 {
01467 avcodec_close(m_codeccontext);
01468 av_free(m_codeccontext);
01469 }
01470 m_codec = NULL;
01471 m_codeccontext = NULL;
01472 }
01473
01474 bool MythRAOPConnection::OpenAudioDevice(void)
01475 {
01476 CloseAudioDevice();
01477
01478 QString passthru = gCoreContext->GetNumSetting("PassThruDeviceOverride", false)
01479 ? gCoreContext->GetSetting("PassThruOutputDevice") : QString::null;
01480 QString device = gCoreContext->GetSetting("AudioOutputDevice");
01481
01482 m_audio = AudioOutput::OpenAudio(device, passthru, FORMAT_S16, m_channels,
01483 0, m_frameRate, AUDIOOUTPUT_MUSIC,
01484 m_allowVolumeControl, false);
01485 if (!m_audio)
01486 {
01487 LOG(VB_GENERAL, LOG_ERR, LOC +
01488 "Failed to open audio device. Going silent...");
01489 CloseAudioDevice();
01490 StartAudioTimer();
01491 return false;
01492 }
01493
01494 QString error = m_audio->GetError();
01495 if (!error.isEmpty())
01496 {
01497 LOG(VB_GENERAL, LOG_ERR, LOC +
01498 QString("Audio not initialised. Message was '%1'")
01499 .arg(error));
01500 CloseAudioDevice();
01501 StartAudioTimer();
01502 return false;
01503 }
01504
01505 StopAudioTimer();
01506 LOG(VB_GENERAL, LOG_DEBUG, LOC + "Opened audio device.");
01507 return true;
01508 }
01509
01510 void MythRAOPConnection::CloseAudioDevice(void)
01511 {
01512 delete m_audio;
01513 m_audio = NULL;
01514 }
01515
01516 void MythRAOPConnection::StartAudioTimer(void)
01517 {
01518 if (m_audioTimer)
01519 return;
01520
01521 m_audioTimer = new QTimer();
01522 connect(m_audioTimer, SIGNAL(timeout()), this, SLOT(audioRetry()));
01523 m_audioTimer->start(5000);
01524 }
01525
01526 void MythRAOPConnection::StopAudioTimer(void)
01527 {
01528 if (m_audioTimer)
01529 {
01530 m_audioTimer->stop();
01531 }
01532 delete m_audioTimer;
01533 m_audioTimer = NULL;
01534 }
01535
01540 int64_t MythRAOPConnection::AudioCardLatency(void)
01541 {
01542 if (!m_audio)
01543 return 0;
01544
01545 uint64_t timestamp = 123456;
01546
01547 int16_t *samples = (int16_t *)av_mallocz(AVCODEC_MAX_AUDIO_FRAME_SIZE);
01548 int frames = AUDIOCARD_BUFFER * m_frameRate / 1000;
01549 m_audio->AddData((char *)samples,
01550 frames * (m_sampleSize>>3) * m_channels,
01551 timestamp,
01552 frames);
01553 av_free(samples);
01554 usleep(AUDIOCARD_BUFFER * 1000);
01555 uint64_t audiots = m_audio->GetAudiotime();
01556 return (int64_t)timestamp - (int64_t)audiots;
01557 }