842 changes: 842 additions & 0 deletions mythtv/libs/libmythtv/mythraopconnection.cpp

Large diffs are not rendered by default.

99 changes: 99 additions & 0 deletions mythtv/libs/libmythtv/mythraopconnection.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#ifndef MYTHRAOPCONNECTION_H
#define MYTHRAOPCONNECTION_H

#include <QObject>
#include <QMap>
#include <QHash>
#include <QHostAddress>

#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/aes.h>

extern "C" {
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
}

class QTextStream;
class QTcpSocket;
class QUdpSocket;
class QTimer;
class AudioOutput;

typedef QHash<QByteArray,QByteArray> RawHash;

class MythRAOPConnection : public QObject
{
Q_OBJECT

public:
MythRAOPConnection(QTcpSocket* socket, QByteArray id, int port);
~MythRAOPConnection();
bool Init(void);
QTcpSocket* GetSocket() { return m_socket; }
int GetDataPort() { return m_dataPort; }

public slots:
void readClient(void);
void udpDataReady(void);
void timeout(void);

private:
uint64_t FramesToMs(uint64_t timestamp);
void ProcessSyncPacket(const QByteArray &buf, uint64_t timenow);
void SendResendRequest(uint64_t timenow, uint16_t expected, uint16_t got);
void ExpireResendRequests(uint64_t timenow);
int ExpireAudio(uint64_t timestamp);
void ProcessAudio(uint64_t timenow);
void ResetAudio(void);
void ProcessRequest(const QList<QByteArray> &lines);
void StartResponse(QTextStream *stream);
void FinishResponse(QTextStream *stream, QTcpSocket *socket,
QByteArray &option, QByteArray &cseq);
RawHash FindTags(const QList<QByteArray> &lines);
static RSA* LoadKey(void);
bool CreateDecoder(void);
void DestroyDecoder(void);
bool OpenAudioDevice(void);
void CloseAudioDevice(void);

QTimer *m_watchdogTimer;
// comms socket
QTcpSocket *m_socket;
QTextStream *m_textStream;
QByteArray m_hardwareId;
// incoming audio
QHostAddress m_peerAddress;
int m_dataPort;
QUdpSocket *m_dataSocket;
QUdpSocket *m_clientControlSocket;
int m_clientControlPort;
QMap<uint16_t,uint64_t> m_resends;
// crypto
QByteArray m_AESIV;
AES_KEY m_aesKey;
static RSA *g_rsa;
// audio out
AudioOutput *m_audio;
AVCodec *m_codec;
AVCodecContext *m_codeccontext;
QList<int> m_audioFormat;
int m_sampleRate;
int m_framesPerPacket;
QMap<uint64_t, int16_t*> m_audioQueue;
bool m_allowVolumeControl;
// audio/packet sync
bool m_seenPacket;
int16_t m_lastPacketSequence;
uint64_t m_lastPacketTimestamp;
uint64_t m_lastSyncTime;
uint64_t m_lastSyncTimestamp;
uint64_t m_lastLatency;
uint64_t m_latencyAudio;
uint64_t m_latencyQueued;
uint64_t m_latencyCounter;
int64_t m_avSync;
};

#endif // MYTHRAOPCONNECTION_H
233 changes: 233 additions & 0 deletions mythtv/libs/libmythtv/mythraopdevice.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
#include <QTimer>
#include <QtEndian>
#include <QTcpSocket>

#include "mthread.h"
#include "mythlogging.h"
#include "mythcorecontext.h"

#include "bonjourregister.h"
#include "mythraopconnection.h"
#include "mythraopdevice.h"

MythRAOPDevice* MythRAOPDevice::gMythRAOPDevice = NULL;
MThread* MythRAOPDevice::gMythRAOPDeviceThread = NULL;
QMutex* MythRAOPDevice::gMythRAOPDeviceMutex = new QMutex(QMutex::Recursive);

#define LOC QString("RAOP Device: ")

bool MythRAOPDevice::Create(void)
{
QMutexLocker locker(gMythRAOPDeviceMutex);

// create the device thread
if (!gMythRAOPDeviceThread)
gMythRAOPDeviceThread = new MThread("RAOPDevice");
if (!gMythRAOPDeviceThread)
{
LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create RAOP device thread.");
return false;
}

// create the device object
if (!gMythRAOPDevice)
gMythRAOPDevice = new MythRAOPDevice();
if (!gMythRAOPDevice)
{
LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create RAOP device object.");
return false;
}

// start the thread
if (!gMythRAOPDeviceThread->isRunning())
{
gMythRAOPDevice->moveToThread(gMythRAOPDeviceThread->qthread());
QObject::connect(
gMythRAOPDeviceThread->qthread(), SIGNAL(started()),
gMythRAOPDevice, SLOT(Start()));
gMythRAOPDeviceThread->start(QThread::LowestPriority);
}

LOG(VB_GENERAL, LOG_INFO, LOC + "Created RAOP device objects.");
return true;
}

void MythRAOPDevice::Cleanup(void)
{
LOG(VB_GENERAL, LOG_INFO, LOC + "Cleaning up.");

if (gMythRAOPDevice)
gMythRAOPDevice->Teardown();

QMutexLocker locker(gMythRAOPDeviceMutex);
if (gMythRAOPDeviceThread)
{
gMythRAOPDeviceThread->exit();
gMythRAOPDeviceThread->wait();
}
delete gMythRAOPDeviceThread;
gMythRAOPDeviceThread = NULL;

delete gMythRAOPDevice;
gMythRAOPDevice = NULL;
}

MythRAOPDevice::MythRAOPDevice()
: QTcpServer(), m_name(QString("MythTV")), m_bonjour(NULL), m_valid(false),
m_lock(new QMutex(QMutex::Recursive)), m_setupPort(5000)
{
for (int i = 0; i < RAOP_HARDWARE_ID_SIZE; i++)
m_hardwareId.append((random() % 80) + 33);
}

MythRAOPDevice::~MythRAOPDevice()
{
Teardown();

delete m_lock;
m_lock = NULL;
}

void MythRAOPDevice::Teardown(void)
{
QMutexLocker locker(m_lock);

// invalidate
m_valid = false;

// disconnect from mDNS
delete m_bonjour;
m_bonjour = NULL;

// disconnect clients
foreach (MythRAOPConnection* client, m_clients)
{
disconnect(client->GetSocket(), 0, 0, 0);
delete client;
}
m_clients.clear();
}

void MythRAOPDevice::Start(void)
{
QMutexLocker locker(m_lock);

// already started?
if (m_valid)
return;

// join the dots
connect(this, SIGNAL(newConnection()), this, SLOT(newConnection()));

// start listening for connections (try a few ports in case the default is in use)
int baseport = m_setupPort;
while (m_setupPort < baseport + RAOP_PORT_RANGE)
{
if (listen(QHostAddress::Any, m_setupPort))
{
LOG(VB_GENERAL, LOG_INFO, LOC +
QString("Listening for connections on port %1").arg(m_setupPort));
break;
}
m_setupPort++;
}

if (m_setupPort >= baseport + RAOP_PORT_RANGE)
LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to find a port for incoming connections.");

// announce service
m_bonjour = new BonjourRegister(this);
if (!m_bonjour)
{
LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create Bonjour object.");
return;
}

// give each frontend a unique name
int multiple = m_setupPort - baseport;
if (multiple > 0)
m_name += QString::number(multiple);

QByteArray name = m_hardwareId.toHex();
name.append("@");
name.append(m_name);
name.append(" on ");
name.append(gCoreContext->GetHostName());
QByteArray type = "_raop._tcp";
QByteArray txt;
txt.append(6); txt.append("tp=UDP");
txt.append(8); txt.append("sm=false");
txt.append(8); txt.append("sv=false");
txt.append(4); txt.append("ek=1");
txt.append(6); txt.append("et=0,1");
txt.append(6); txt.append("cn=0,1");
txt.append(4); txt.append("ch=2");
txt.append(5); txt.append("ss=16");
txt.append(8); txt.append("sr=44100");
txt.append(8); txt.append("pw=false");
txt.append(4); txt.append("vn=3");
txt.append(9); txt.append("txtvers=1");

if (!m_bonjour->Register(m_setupPort, type, name, txt))
{
LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to register service.");
return;
}

m_valid = true;
return;
}

void MythRAOPDevice::newConnection(void)
{
QMutexLocker locker(m_lock);
QTcpSocket *client = this->nextPendingConnection();
LOG(VB_GENERAL, LOG_INFO, LOC + QString("New connection from %1:%2")
.arg(client->peerAddress().toString()).arg(client->peerPort()));

int port = 6000;
while (port < (6000 + RAOP_PORT_RANGE))
{
bool found = false;
foreach (MythRAOPConnection* client, m_clients)
{
if (client->GetDataPort() == port)
{
found = true;
port++;
}
}
if (!found)
break;
}

MythRAOPConnection *obj =
new MythRAOPConnection(client, m_hardwareId, port);
if (obj->Init())
{
m_clients.append(obj);
connect(client, SIGNAL(disconnected()), this, SLOT(deleteClient()));
return;
}

LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to initialise client connection - closing.");
delete obj;
client->disconnectFromHost();
delete client;
}

void MythRAOPDevice::deleteClient(void)
{
QMutexLocker locker(m_lock);
QList<MythRAOPConnection *>::iterator it;
for (it = m_clients.begin(); it != m_clients.end(); ++it)
{
if ((*it)->GetSocket()->state() == QTcpSocket::UnconnectedState)
{
LOG(VB_GENERAL, LOG_INFO, LOC + "Removing client connection.");
delete *it;
m_clients.removeOne(*it);
return;
}
}
}
52 changes: 52 additions & 0 deletions mythtv/libs/libmythtv/mythraopdevice.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#ifndef MYTHRAOPDEVICE_H
#define MYTHRAOPDEVICE_H

#include <QObject>
#include <QTcpServer>

#include "mythtvexp.h"

class QMutex;
class MThread;
class BonjourRegister;
class MythRAOPConnection;

#define RAOP_PORT_RANGE 100
#define RAOP_HARDWARE_ID_SIZE 6

class MTV_PUBLIC MythRAOPDevice : public QTcpServer
{
Q_OBJECT

public:
static bool Create(void);
static void Cleanup(void);

MythRAOPDevice();

private slots:
void Start();
void newConnection();
void deleteClient();

private:
virtual ~MythRAOPDevice(void);
void Teardown(void);

// Globals
static MythRAOPDevice *gMythRAOPDevice;
static QMutex *gMythRAOPDeviceMutex;
static MThread *gMythRAOPDeviceThread;

// Members
QString m_name;
QByteArray m_hardwareId;
BonjourRegister *m_bonjour;
bool m_valid;
QMutex *m_lock;
int m_setupPort;
QList<MythRAOPConnection*> m_clients;
};


#endif // MYTHRAOPDEVICE_H
11 changes: 11 additions & 0 deletions mythtv/programs/mythfrontend/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ using namespace std;
#include "videometadatasettings.h"
#include "videolist.h"

#ifdef USING_RAOP
#include "mythraopdevice.h"
#endif

static ExitPrompter *exitPopup = NULL;
static MythThemedMenu *menu;
Expand Down Expand Up @@ -216,6 +219,10 @@ namespace

void cleanup()
{
#ifdef USING_RAOP
MythRAOPDevice::Cleanup();
#endif

delete exitPopup;
exitPopup = NULL;

Expand Down Expand Up @@ -1540,6 +1547,10 @@ int main(int argc, char **argv)

setuid(getuid());

#ifdef USING_RAOP
MythRAOPDevice::Create();
#endif

LCD::SetupLCD();
if (LCD *lcd = LCD::Get())
lcd->setupLEDs(RemoteGetRecordingMask);
Expand Down
1 change: 1 addition & 0 deletions mythtv/programs/mythfrontend/mythfrontend.pro
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,4 @@ using_alsa:DEFINES += USING_ALSA
using_jack:DEFINES += USING_JACK
using_oss: DEFINES += USING_OSS
macx: DEFINES += USING_COREAUDIO
using_libdns_sd: using_libcrypto: DEFINES += USING_RAOP