Commit | Line | Data |
---|---|---|
23e7e3ae JVH |
1 | /** |
2 | * Copyright (C) 2011-2012 Juho Vähä-Herttua | |
3 | * | |
4 | * This library is free software; you can redistribute it and/or | |
5 | * modify it under the terms of the GNU Lesser General Public | |
6 | * License as published by the Free Software Foundation; either | |
7 | * version 2.1 of the License, or (at your option) any later version. | |
8 | * | |
9 | * This library is distributed in the hope that it will be useful, | |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
12 | * Lesser General Public License for more details. | |
13 | */ | |
14 | ||
2340bcd3 JVH |
15 | #include "raopservice.h" |
16 | ||
17 | #include <QDebug> | |
18 | #include <QFile> | |
19 | ||
4fb2a472 JVH |
20 | static void* |
21 | audio_init(void *cls, int bits, int channels, int samplerate) | |
2340bcd3 | 22 | { |
4fb2a472 | 23 | void *session; |
2340bcd3 | 24 | QMetaObject::invokeMethod((QObject*)cls, "audioInit", Qt::BlockingQueuedConnection, |
4fb2a472 | 25 | Q_ARG(void*, (void*)&session), |
2340bcd3 JVH |
26 | Q_ARG(int, bits), |
27 | Q_ARG(int, channels), | |
28 | Q_ARG(int, samplerate)); | |
4fb2a472 | 29 | return session; |
2340bcd3 JVH |
30 | } |
31 | ||
32 | static void | |
33 | audio_set_volume(void *cls, void *session, float volume) | |
34 | { | |
35 | QMetaObject::invokeMethod((QObject*)cls, "audioSetVolume", Qt::BlockingQueuedConnection, | |
36 | Q_ARG(void*, session), | |
37 | Q_ARG(float, volume)); | |
38 | } | |
39 | ||
40 | static void | |
41 | audio_process(void *cls, void *session, const void *buffer, int buflen) | |
42 | { | |
43 | QMetaObject::invokeMethod((QObject*)cls, "audioProcess", Qt::BlockingQueuedConnection, | |
44 | Q_ARG(void*, session), | |
45 | Q_ARG(void*, (void*)buffer), | |
46 | Q_ARG(int, buflen)); | |
47 | } | |
48 | ||
49 | static void | |
50 | audio_flush(void *cls, void *session) | |
51 | { | |
52 | QMetaObject::invokeMethod((QObject*)cls, "audioFlush", Qt::BlockingQueuedConnection, | |
53 | Q_ARG(void*, session)); | |
54 | } | |
55 | ||
56 | static void | |
57 | audio_destroy(void *cls, void *session) | |
58 | { | |
59 | QMetaObject::invokeMethod((QObject*)cls, "audioDestroy", Qt::BlockingQueuedConnection, | |
60 | Q_ARG(void*, session)); | |
61 | } | |
62 | ||
63 | RaopService::RaopService(QObject *parent) : | |
64 | QObject(parent), | |
65 | m_dnssd(0), | |
66 | m_raop(0) | |
67 | { | |
68 | /* This whole hack is required because QAudioOutput | |
69 | * needs to be created in a QThread, threads created | |
70 | * outside Qt are not allowed (they have no eventloop) */ | |
71 | m_handler.moveToThread(&m_thread); | |
72 | } | |
73 | ||
74 | RaopService::~RaopService() | |
75 | { | |
76 | this->stop(); | |
77 | ||
78 | dnssd_destroy(m_dnssd); | |
79 | raop_destroy(m_raop); | |
80 | } | |
81 | ||
82 | bool RaopService::init() | |
83 | { | |
2340bcd3 JVH |
84 | raop_callbacks_t raop_cbs; |
85 | int error; | |
86 | ||
87 | raop_cbs.cls = &m_handler; | |
88 | raop_cbs.audio_init = audio_init; | |
89 | raop_cbs.audio_set_volume = audio_set_volume; | |
90 | raop_cbs.audio_process = audio_process; | |
91 | raop_cbs.audio_flush = audio_flush; | |
92 | raop_cbs.audio_destroy = audio_destroy; | |
93 | ||
94 | QFile file("airport.key"); | |
95 | if (!file.exists()) { | |
96 | // This is used when running from Qt Creator on Mac | |
97 | file.setFileName("../../../../airport.key"); | |
98 | } | |
99 | if (!file.exists()) { | |
100 | // This is used when running from Qt Creator on Windows | |
101 | file.setFileName("../airport.key"); | |
102 | } | |
103 | if (!file.exists()) { | |
104 | return false; | |
105 | } | |
106 | file.open(QIODevice::ReadOnly); | |
107 | QByteArray array = file.read(file.size()); | |
108 | array.append('\0'); | |
109 | ||
406e9777 | 110 | m_raop = raop_init(&raop_cbs, array.data()); |
2340bcd3 JVH |
111 | if (!m_raop) { |
112 | return false; | |
113 | } | |
114 | ||
4fb2a472 | 115 | m_dnssd = dnssd_init(&error); |
2340bcd3 JVH |
116 | if (!m_dnssd) { |
117 | raop_destroy(m_raop); | |
118 | m_raop = NULL; | |
119 | return false; | |
120 | } | |
121 | ||
122 | return true; | |
123 | } | |
124 | ||
125 | bool RaopService::start(const QString & name, quint16 port) | |
126 | { | |
406e9777 JVH |
127 | const char hwaddr[] = { 0x48, 0x5d, 0x60, 0x7c, 0xee, 0x22 }; |
128 | ||
2340bcd3 JVH |
129 | if (!m_raop || !m_dnssd || m_thread.isRunning()) { |
130 | return false; | |
131 | } | |
132 | ||
133 | m_thread.start(); | |
406e9777 | 134 | if (raop_start(m_raop, &port, hwaddr, sizeof(hwaddr)) < 0) { |
2340bcd3 JVH |
135 | m_thread.quit(); |
136 | m_thread.wait(); | |
137 | return false; | |
138 | } | |
4fb2a472 | 139 | if (dnssd_register_raop(m_dnssd, name.toUtf8(), port, hwaddr, sizeof(hwaddr)) < 0) { |
2340bcd3 JVH |
140 | raop_stop(m_raop); |
141 | m_thread.quit(); | |
142 | m_thread.wait(); | |
143 | return false; | |
144 | } | |
145 | ||
146 | return true; | |
147 | } | |
148 | ||
149 | void RaopService::stop() | |
150 | { | |
151 | if (m_dnssd) { | |
152 | dnssd_unregister_raop(m_dnssd); | |
153 | } | |
154 | if (m_raop) { | |
155 | raop_stop(m_raop); | |
156 | } | |
157 | if (m_thread.isRunning()) { | |
158 | m_thread.quit(); | |
159 | m_thread.wait(); | |
160 | } | |
161 | } | |
162 |