MythTV  0.26-pre
mythraopconnection.cpp
Go to the documentation of this file.
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 // RAOP RTP packet type
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 // Size (in ms) of audio buffered in audio card
00033 #define AUDIOCARD_BUFFER 800
00034 // How frequently we may call ProcessAudio (via QTimer)
00035 // ideally 20ms, but according to documentation
00036 // anything lower than 50ms on windows, isn't reliable
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     //audio sync
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     // clock sync
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     // delete audio timer
00086     StopAudioTimer();
00087 
00088     // stop and delete watchdog timer
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     // delete main socket
00102     if (m_socket)
00103     {
00104         m_socket->close();
00105         m_socket->deleteLater();
00106     }
00107 
00108     // delete data socket
00109     if (m_dataSocket)
00110     {
00111         m_dataSocket->disconnect();
00112         m_dataSocket->close();
00113         m_dataSocket->deleteLater();
00114     }
00115 
00116     // client control socket
00117     if (m_clientControlSocket)
00118     {
00119         m_clientControlSocket->disconnect();
00120         m_clientControlSocket->close();
00121         m_clientControlSocket->deleteLater();
00122     }
00123 
00124     // close audio decoder
00125     DestroyDecoder();
00126 
00127     // free decoded audio buffer
00128     ResetAudio();
00129 
00130     // close audio device
00131     CloseAudioDevice();
00132 }
00133 
00134 bool MythRAOPConnection::Init(void)
00135 {
00136     // connect up the request socket
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     // create the data socket
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     // try a few ports in case the first is in use
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     // load the private key
00166     if (!LoadKey())
00167         return false;
00168 
00169     // use internal volume control
00170     m_allowVolumeControl = gCoreContext->GetNumSetting("MythControlsVolume", 1);
00171 
00172     // start the watchdog timer to auto delete the client after a period of inactivity
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     // restart the idle timer
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             // With iTunes we know what the first sequence is going to be.
00221             // iOS device do not tell us before streaming start what the first
00222             // packet is going to be.
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     // regular data packet
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     // resent packet
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     // Check that the audio packet is valid, do so by decoding it. If an error
00280     // occurs, ask to resend it
00281     QList<AudioData> *decoded = new QList<AudioData>();
00282     int numframes = decodeAudioPacket(type, &buf, decoded);
00283     if (numframes < 0)
00284     {
00285         // an error occurred, ask for the audio packet once again.
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; // First sync is 0x90,0x55
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     // Calculate audio card latency
00330     if (first)
00331     {
00332         m_cardLatency = AudioCardLatency();
00333         // if audio isn't started, start playing 200ms worth of silence
00334         // and measure timestamp difference
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         // Too much delay in playback
00347         // will reset audio card in next ProcessAudio
00348         m_audioStarted = false;
00349         m_adjustedLatency = 0;
00350     }
00351 
00352     delay += m_audio->GetAudioBufferedTime();
00353     delay += m_adjustedLatency;
00354 
00355     // Expire old audio
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);   // missed seqnum
00394     *(uint16_t *)(req + 6) = qToBigEndian(missed);     // count
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) // should never happen
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     // this is always 0x00 0x07 according to http://blog.technologeek.org/airtunes-v2
00450     // no other value works
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     // network latency equal time difference in ms between request and response
00490     // divide by two for approximate time of one way trip
00491     m_networkLatency = (time2 - time1) / 2;
00492 
00493     // now calculate the time difference between the client and us.
00494     // this is NTP time, where sec is in seconds, and ticks is in 1/2^32s
00495     uint32_t sec    = qFromBigEndian(*(uint32_t*)(req + 24));
00496     uint32_t ticks  = qFromBigEndian(*(uint32_t*)(req + 28));
00497     // convert ticks into ms
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 &timestamp)
00509 {
00510     // All RAOP packets start with | 0x80/0x90 (first sync) | PACKET_TYPE |
00511     if ((uint8_t)buf[0] != 0x80 && (uint8_t)buf[0] != 0x90)
00512     {
00513         return false;
00514     }
00515 
00516     type = (char)buf[1];
00517     // Is it first sync packet?
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 // Audio decode / playback related routines
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             // ALAC codec isn't planar
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         // ALSA takes a while to unpause, enough to have SYNC starting to drop
00618         // packets, so unpause as early as possible
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     // Keep audio framework buffer as short as possible, keeping everything in
00627     // m_audioQueue, so we can easily reset the least amount possible
00628     if (buffered > AUDIOCARD_BUFFER)
00629         return;
00630 
00631     // Also make sure m_audioQueue never goes to less than 1/3 of the RDP stream
00632     // total latency, this should gives us enough time to receive missed packets
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     // How many packets to add to the audio card, to fill AUDIOCARD_BUFFER
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(); // clear audio card
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 // QMap is sorted, so no need to continue if not found
00681             break;
00682     }
00683 
00684     ExpireAudio(timestamp);
00685     m_lastTimestamp = timestamp;
00686 
00687     // restart audio timer should we stop receiving data on regular interval,
00688     // we need to continue processing the audio queue
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     // For big content, we may be called several times for a single packet
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     // If we haven't received all the content yet, wait (see when receiving
00807     // coverart
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     // process RTP-info field
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             // Recreate transport line with new ports value
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         // Ask for master clock value to determine time skew and average network latency
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         // determine RTP timestamp of last sample played
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                 // Receiving image coverart
01216                 m_artwork = content;
01217             }
01218             else if (tags["Content-Type"] == "application/x-dmap-tagged")
01219             {
01220                 // Receiving DMAP metadata
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     // create an ALAC decoder
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]; // frame size
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;       // channels
01442             extradata[17] = m_audioFormat[3]; // sample size
01443             extradata[18] = m_audioFormat[4]; // rice_historymult
01444             extradata[19] = m_audioFormat[5]; // rice_initialhistory
01445             extradata[20] = m_audioFormat[6]; // rice_kmodifier
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 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends