| 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 | |