1 | /* |
2 | Copyright (C) 2007-2008 Tanguy Krotoff <tkrotoff@gmail.com> |
3 | Copyright (C) 2008 Lukas Durfina <lukas.durfina@gmail.com> |
4 | Copyright (C) 2009 Fathi Boudra <fabo@kde.org> |
5 | Copyright (C) 2009-2010 vlc-phonon AUTHORS <kde-multimedia@kde.org> |
6 | Copyright (C) 2011 Harald Sitter <sitter@kde.org> |
7 | |
8 | This library is free software; you can redistribute it and/or |
9 | modify it under the terms of the GNU Lesser General Public |
10 | License as published by the Free Software Foundation; either |
11 | version 2.1 of the License, or (at your option) any later version. |
12 | |
13 | This library is distributed in the hope that it will be useful, |
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
16 | Lesser General Public License for more details. |
17 | |
18 | You should have received a copy of the GNU Lesser General Public |
19 | License along with this library. If not, see <http://www.gnu.org/licenses/>. |
20 | */ |
21 | |
22 | #include "devicemanager.h" |
23 | |
24 | #include <phonon/pulsesupport.h> |
25 | |
26 | #include <vlc/vlc.h> |
27 | |
28 | #include "backend.h" |
29 | #include "utils/debug.h" |
30 | #include "utils/libvlc.h" |
31 | #include "utils/vstring.h" |
32 | |
33 | namespace Phonon |
34 | { |
35 | namespace VLC |
36 | { |
37 | |
38 | /* |
39 | * Device Info |
40 | */ |
41 | |
42 | DeviceInfo::DeviceInfo(const QString &name, bool isAdvanced) |
43 | { |
44 | // Get an id |
45 | static int counter = 0; |
46 | m_id = counter++; |
47 | |
48 | // Get name and description for the device |
49 | m_name = name; |
50 | m_isAdvanced = isAdvanced; |
51 | m_capabilities = None; |
52 | |
53 | // A default device should never be advanced |
54 | if (name.startsWith(s: QLatin1String("default" ), cs: Qt::CaseInsensitive)) |
55 | m_isAdvanced = false; |
56 | } |
57 | |
58 | int DeviceInfo::id() const |
59 | { |
60 | return m_id; |
61 | } |
62 | |
63 | const QString& DeviceInfo::name() const |
64 | { |
65 | return m_name; |
66 | } |
67 | |
68 | const QString& DeviceInfo::description() const |
69 | { |
70 | return m_description; |
71 | } |
72 | |
73 | bool DeviceInfo::isAdvanced() const |
74 | { |
75 | return m_isAdvanced; |
76 | } |
77 | |
78 | void DeviceInfo::setAdvanced(bool advanced) |
79 | { |
80 | m_isAdvanced = advanced; |
81 | } |
82 | |
83 | const DeviceAccessList& DeviceInfo::accessList() const |
84 | { |
85 | return m_accessList; |
86 | } |
87 | |
88 | void DeviceInfo::addAccess(const DeviceAccess& access) |
89 | { |
90 | if (m_accessList.isEmpty()) |
91 | m_description = access.first + ": " + access.second; |
92 | m_accessList.append(t: access); |
93 | } |
94 | |
95 | quint16 DeviceInfo::capabilities() const |
96 | { |
97 | return m_capabilities; |
98 | } |
99 | |
100 | void DeviceInfo::setCapabilities(quint16 cap) |
101 | { |
102 | m_capabilities = cap; |
103 | } |
104 | |
105 | |
106 | /* |
107 | * Device Manager |
108 | */ |
109 | |
110 | DeviceManager::DeviceManager(Backend *parent) |
111 | : QObject(parent) |
112 | , m_backend(parent) |
113 | { |
114 | Q_ASSERT(parent); |
115 | updateDeviceList(); |
116 | } |
117 | |
118 | DeviceManager::~DeviceManager() |
119 | { |
120 | } |
121 | |
122 | QList<int> DeviceManager::deviceIds(ObjectDescriptionType type) |
123 | { |
124 | DeviceInfo::Capability capability = DeviceInfo::None; |
125 | switch (type) { |
126 | case Phonon::AudioOutputDeviceType: |
127 | capability = DeviceInfo::AudioOutput; |
128 | break; |
129 | case Phonon::AudioCaptureDeviceType: |
130 | capability = DeviceInfo::AudioCapture; |
131 | break; |
132 | case Phonon::VideoCaptureDeviceType: |
133 | capability = DeviceInfo::VideoCapture; |
134 | break; |
135 | default: ; |
136 | } |
137 | |
138 | QList<int> ids; |
139 | foreach (const DeviceInfo &device, m_devices) { |
140 | if (device.capabilities() & capability) |
141 | ids.append(t: device.id()); |
142 | } |
143 | |
144 | return ids; |
145 | } |
146 | |
147 | QHash<QByteArray, QVariant> DeviceManager::deviceProperties(int id) |
148 | { |
149 | QHash<QByteArray, QVariant> properties; |
150 | |
151 | foreach (const DeviceInfo &device, m_devices) { |
152 | if (device.id() == id) { |
153 | properties.insert(key: "name" , value: device.name()); |
154 | properties.insert(key: "description" , value: device.description()); |
155 | properties.insert(key: "isAdvanced" , value: device.isAdvanced()); |
156 | properties.insert(key: "deviceAccessList" , value: QVariant::fromValue<Phonon::DeviceAccessList>(value: device.accessList())); |
157 | properties.insert(key: "discovererIcon" , value: "vlc" ); |
158 | |
159 | if (device.capabilities() & DeviceInfo::AudioOutput) { |
160 | properties.insert(key: "icon" , value: QLatin1String("audio-card" )); |
161 | } |
162 | |
163 | if (device.capabilities() & DeviceInfo::AudioCapture) { |
164 | properties.insert(key: "hasaudio" , value: true); |
165 | properties.insert(key: "icon" , value: QLatin1String("audio-input-microphone" )); |
166 | } |
167 | |
168 | if (device.capabilities() & DeviceInfo::VideoCapture) { |
169 | properties.insert(key: "hasvideo" , value: true); |
170 | properties.insert(key: "icon" , value: QLatin1String("camera-web" )); |
171 | } |
172 | break; |
173 | } |
174 | } |
175 | |
176 | return properties; |
177 | } |
178 | |
179 | const DeviceInfo *DeviceManager::device(int id) const |
180 | { |
181 | for (int i = 0; i < m_devices.size(); i ++) { |
182 | if (m_devices[i].id() == id) |
183 | return &m_devices[i]; |
184 | } |
185 | |
186 | return NULL; |
187 | } |
188 | |
189 | static QList<QByteArray> vlcAudioOutBackends() |
190 | { |
191 | QList<QByteArray> ret; |
192 | |
193 | VLC_FOREACH_LIST(audio_output, aout) { |
194 | QByteArray name(aout->psz_name); |
195 | if (!ret.contains(t: name)) |
196 | ret.append(t: name); |
197 | } |
198 | |
199 | return ret; |
200 | } |
201 | |
202 | void DeviceManager::updateDeviceList() |
203 | { |
204 | QList<DeviceInfo> newDeviceList; |
205 | |
206 | if (!LibVLC::self || !pvlc_libvlc) |
207 | return; |
208 | |
209 | QList<QByteArray> audioOutBackends = vlcAudioOutBackends(); |
210 | |
211 | PulseSupport *pulse = PulseSupport::getInstance(); |
212 | if (pulse && pulse->isUsable()) { |
213 | if (audioOutBackends.contains(t: "pulse" )) { |
214 | DeviceInfo defaultAudioOutputDevice(tr(s: "Default" ), false); |
215 | defaultAudioOutputDevice.setCapabilities(DeviceInfo::AudioOutput); |
216 | defaultAudioOutputDevice.addAccess(access: DeviceAccess("pulse" , "default" )); |
217 | newDeviceList.append(t: defaultAudioOutputDevice); |
218 | pulse->request(requested: true); |
219 | return; |
220 | } else { |
221 | pulse->enable(enabled: false); |
222 | } |
223 | } |
224 | |
225 | QList<QByteArray> knownSoundSystems; |
226 | // Whitelist - Order has no particular impact. |
227 | // NOTE: if listing was not intercepted by the PA code above we also need |
228 | // to try injecting the pulse aout as otherwise the user would have to |
229 | // use the fake PA device in ALSA to output through PA (kind of silly). |
230 | knownSoundSystems << QByteArray("pulse" ) |
231 | << QByteArray("alsa" ) |
232 | << QByteArray("oss" ) |
233 | << QByteArray("jack" ) |
234 | << QByteArray("aout_directx" ) // Windows up to VLC 2.0 |
235 | << QByteArray("directsound" ) // Windows from VLC 2.1 upwards |
236 | << QByteArray("auhal" ); // Mac |
237 | foreach (const QByteArray &soundSystem, knownSoundSystems) { |
238 | if (!audioOutBackends.contains(t: soundSystem)) { |
239 | debug() << "Sound system" << soundSystem << "not supported by libvlc" ; |
240 | continue; |
241 | } |
242 | |
243 | // FIXME: there is a rather ungodly amount of code duplication going |
244 | // on here. |
245 | bool hasDevices = false; |
246 | VLC_FOREACH(audio_output_device, |
247 | device, |
248 | libvlc_audio_output_device_list_get(pvlc_libvlc, soundSystem), |
249 | libvlc_audio_output_device_list_release) { |
250 | QString idName = QString::fromUtf8(utf8: device->psz_device); |
251 | QString longName = QString::fromUtf8(utf8: device->psz_description); |
252 | |
253 | debug() << "found device" << soundSystem << idName << longName; |
254 | |
255 | DeviceInfo info(longName, true); |
256 | info.addAccess(access: DeviceAccess(soundSystem, idName)); |
257 | info.setCapabilities(DeviceInfo::AudioOutput); |
258 | newDeviceList.append(t: info); |
259 | |
260 | hasDevices = true; |
261 | } |
262 | |
263 | if (!hasDevices) { |
264 | debug() << "manually injecting sound system" << soundSystem; |
265 | DeviceInfo info(QString::fromUtf8(ba: soundSystem), false); |
266 | info.addAccess(access: DeviceAccess(soundSystem, "" )); |
267 | info.setCapabilities(DeviceInfo::AudioOutput); |
268 | newDeviceList.append(t: info); |
269 | } |
270 | } |
271 | |
272 | /* |
273 | * Compares the list with the devices available at the moment with the last list. If |
274 | * a new device is seen, a signal is emitted. If a device disappeared, another signal |
275 | * is emitted. |
276 | */ |
277 | |
278 | // Search for added devices |
279 | for (int i = 0; i < newDeviceList.count(); ++i) { |
280 | int id = newDeviceList[i].id(); |
281 | if (!listContainsDevice(list: m_devices, id)) { |
282 | // This is a new device, add it |
283 | m_devices.append(t: newDeviceList[i]); |
284 | emit deviceAdded(id); |
285 | |
286 | debug() << "Added backend device" << newDeviceList[i].name(); |
287 | } |
288 | } |
289 | |
290 | // Search for removed devices |
291 | for (int i = m_devices.count() - 1; i >= 0; --i) { |
292 | int id = m_devices[i].id(); |
293 | if (!listContainsDevice(list: newDeviceList, id)) { |
294 | emit deviceRemoved(id); |
295 | m_devices.removeAt(i); |
296 | } |
297 | } |
298 | } |
299 | |
300 | bool DeviceManager::listContainsDevice(const QList<DeviceInfo> &list, int id) |
301 | { |
302 | foreach (const DeviceInfo &d, list) { |
303 | if (d.id() == id) |
304 | return true; |
305 | } |
306 | return false; |
307 | } |
308 | |
309 | } |
310 | } |
311 | |