|
1 /* This file is part of the KDE project. |
|
2 |
|
3 Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). |
|
4 |
|
5 This library is free software: you can redistribute it and/or modify |
|
6 it under the terms of the GNU Lesser General Public License as published by |
|
7 the Free Software Foundation, either version 2.1 or 3 of the License. |
|
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 |
|
12 GNU Lesser General Public License for more details. |
|
13 |
|
14 You should have received a copy of the GNU Lesser General Public License |
|
15 along with this library. If not, see <http://www.gnu.org/licenses/>. |
|
16 */ |
|
17 |
|
18 #include <gst/interfaces/propertyprobe.h> |
|
19 #include "devicemanager.h" |
|
20 #include "backend.h" |
|
21 #include "gsthelper.h" |
|
22 #include "videowidget.h" |
|
23 #include "glrenderer.h" |
|
24 #include "widgetrenderer.h" |
|
25 #include "x11renderer.h" |
|
26 #include "artssink.h" |
|
27 |
|
28 #ifdef USE_ALSASINK2 |
|
29 #include "alsasink2.h" |
|
30 #endif |
|
31 |
|
32 /* |
|
33 * This class manages the list of currently |
|
34 * active output devices |
|
35 */ |
|
36 |
|
37 QT_BEGIN_NAMESPACE |
|
38 |
|
39 namespace Phonon |
|
40 { |
|
41 namespace Gstreamer |
|
42 { |
|
43 |
|
44 AudioDevice::AudioDevice(DeviceManager *manager, const QByteArray &gstId) |
|
45 : gstId(gstId) |
|
46 { |
|
47 //get an id |
|
48 static int counter = 0; |
|
49 id = counter++; |
|
50 //get name from device |
|
51 if (gstId == "default") { |
|
52 description = "Default audio device"; |
|
53 } else { |
|
54 GstElement *aSink= manager->createAudioSink(); |
|
55 |
|
56 if (aSink) { |
|
57 gchar *deviceDescription = NULL; |
|
58 |
|
59 if (GST_IS_PROPERTY_PROBE(aSink) && gst_property_probe_get_property( GST_PROPERTY_PROBE(aSink), "device" ) ) { |
|
60 g_object_set (G_OBJECT(aSink), "device", gstId.constData(), (const char*)NULL); |
|
61 g_object_get (G_OBJECT(aSink), "device-name", &deviceDescription, (const char*)NULL); |
|
62 description = QByteArray(deviceDescription); |
|
63 g_free (deviceDescription); |
|
64 gst_element_set_state(aSink, GST_STATE_NULL); |
|
65 gst_object_unref (aSink); |
|
66 } |
|
67 } |
|
68 } |
|
69 } |
|
70 |
|
71 DeviceManager::DeviceManager(Backend *backend) |
|
72 : QObject(backend) |
|
73 , m_backend(backend) |
|
74 { |
|
75 QSettings settings(QLatin1String("Trolltech")); |
|
76 settings.beginGroup(QLatin1String("Qt")); |
|
77 |
|
78 m_audioSink = qgetenv("PHONON_GST_AUDIOSINK"); |
|
79 if (m_audioSink.isEmpty()) { |
|
80 m_audioSink = settings.value(QLatin1String("audiosink"), "Auto").toByteArray().toLower(); |
|
81 } |
|
82 |
|
83 m_videoSinkWidget = qgetenv("PHONON_GST_VIDEOMODE"); |
|
84 if (m_videoSinkWidget.isEmpty()) { |
|
85 m_videoSinkWidget = settings.value(QLatin1String("videomode"), "Auto").toByteArray().toLower(); |
|
86 } |
|
87 |
|
88 if (m_backend->isValid()) |
|
89 updateDeviceList(); |
|
90 } |
|
91 |
|
92 DeviceManager::~DeviceManager() |
|
93 { |
|
94 m_audioDeviceList.clear(); |
|
95 } |
|
96 |
|
97 /*** |
|
98 * Returns a Gst Audiosink based on GNOME configuration settings, |
|
99 * or 0 if the element is not available. |
|
100 */ |
|
101 GstElement *DeviceManager::createGNOMEAudioSink(Category category) |
|
102 { |
|
103 GstElement *sink = gst_element_factory_make ("gconfaudiosink", NULL); |
|
104 |
|
105 if (sink) { |
|
106 |
|
107 // set profile property on the gconfaudiosink to "music and movies" |
|
108 if (g_object_class_find_property (G_OBJECT_GET_CLASS (sink), "profile")) { |
|
109 switch (category) { |
|
110 case NotificationCategory: |
|
111 g_object_set (G_OBJECT (sink), "profile", 0, (const char*)NULL); // 0 = 'sounds' |
|
112 break; |
|
113 case CommunicationCategory: |
|
114 g_object_set (G_OBJECT (sink), "profile", 2, (const char*)NULL); // 2 = 'chat' |
|
115 break; |
|
116 default: |
|
117 g_object_set (G_OBJECT (sink), "profile", 1, (const char*)NULL); // 1 = 'music and movies' |
|
118 break; |
|
119 } |
|
120 } |
|
121 } |
|
122 return sink; |
|
123 } |
|
124 |
|
125 |
|
126 bool DeviceManager::canOpenDevice(GstElement *element) const |
|
127 { |
|
128 if (!element) |
|
129 return false; |
|
130 |
|
131 if (gst_element_set_state(element, GST_STATE_READY) == GST_STATE_CHANGE_SUCCESS) |
|
132 return true; |
|
133 |
|
134 const QList<QByteArray> &list = GstHelper::extractProperties(element, "device"); |
|
135 foreach (const QByteArray &gstId, list) { |
|
136 GstHelper::setProperty(element, "device", gstId); |
|
137 if (gst_element_set_state(element, GST_STATE_READY) == GST_STATE_CHANGE_SUCCESS) { |
|
138 return true; |
|
139 } |
|
140 } |
|
141 // FIXME: the above can still fail for a valid alsasink because list only contains entries of |
|
142 // the form "hw:X,Y". Would be better to use "default:X" or "dmix:X,Y" |
|
143 |
|
144 gst_element_set_state(element, GST_STATE_NULL); |
|
145 return false; |
|
146 } |
|
147 |
|
148 /* |
|
149 * |
|
150 * Returns a GstElement with a valid audio sink |
|
151 * based on the current value of PHONON_GSTREAMER_DRIVER |
|
152 * |
|
153 * Allowed values are auto (default), alsa, oss, arts and ess |
|
154 * does not exist |
|
155 * |
|
156 * If no real sound sink is available a fakesink will be returned |
|
157 */ |
|
158 GstElement *DeviceManager::createAudioSink(Category category) |
|
159 { |
|
160 GstElement *sink = 0; |
|
161 |
|
162 if (m_backend && m_backend->isValid()) |
|
163 { |
|
164 if (m_audioSink == "auto") //this is the default value |
|
165 { |
|
166 //### TODO : get equivalent KDE settings here |
|
167 |
|
168 if (!qgetenv("GNOME_DESKTOP_SESSION_ID").isEmpty()) { |
|
169 sink = createGNOMEAudioSink(category); |
|
170 if (canOpenDevice(sink)) |
|
171 m_backend->logMessage("AudioOutput using gconf audio sink"); |
|
172 else if (sink) { |
|
173 gst_object_unref(sink); |
|
174 sink = 0; |
|
175 } |
|
176 } |
|
177 |
|
178 #ifdef USE_ALSASINK2 |
|
179 if (!sink) { |
|
180 sink = gst_element_factory_make ("_k_alsasink", NULL); |
|
181 if (canOpenDevice(sink)) |
|
182 m_backend->logMessage("AudioOutput using alsa2 audio sink"); |
|
183 else if (sink) { |
|
184 gst_object_unref(sink); |
|
185 sink = 0; |
|
186 } |
|
187 } |
|
188 #endif |
|
189 |
|
190 if (!sink) { |
|
191 sink = gst_element_factory_make ("alsasink", NULL); |
|
192 if (canOpenDevice(sink)) |
|
193 m_backend->logMessage("AudioOutput using alsa audio sink"); |
|
194 else if (sink) { |
|
195 gst_object_unref(sink); |
|
196 sink = 0; |
|
197 } |
|
198 } |
|
199 |
|
200 if (!sink) { |
|
201 sink = gst_element_factory_make ("autoaudiosink", NULL); |
|
202 if (canOpenDevice(sink)) |
|
203 m_backend->logMessage("AudioOutput using auto audio sink"); |
|
204 else if (sink) { |
|
205 gst_object_unref(sink); |
|
206 sink = 0; |
|
207 } |
|
208 } |
|
209 |
|
210 if (!sink) { |
|
211 sink = gst_element_factory_make ("osssink", NULL); |
|
212 if (canOpenDevice(sink)) |
|
213 m_backend->logMessage("AudioOutput using oss audio sink"); |
|
214 else if (sink) { |
|
215 gst_object_unref(sink); |
|
216 sink = 0; |
|
217 } |
|
218 } |
|
219 } else if (m_audioSink == "fake") { |
|
220 //do nothing as a fakesink will be created by default |
|
221 } else if (m_audioSink == "artssink") { |
|
222 sink = GST_ELEMENT(g_object_new(arts_sink_get_type(), NULL)); |
|
223 } else if (!m_audioSink.isEmpty()) { //Use a custom sink |
|
224 sink = gst_element_factory_make (m_audioSink, NULL); |
|
225 if (canOpenDevice(sink)) |
|
226 m_backend->logMessage(QString("AudioOutput using %0").arg(QString::fromUtf8(m_audioSink))); |
|
227 else if (sink) { |
|
228 gst_object_unref(sink); |
|
229 sink = 0; |
|
230 } |
|
231 } |
|
232 } |
|
233 |
|
234 if (!sink) { //no suitable sink found so we'll make a fake one |
|
235 sink = gst_element_factory_make("fakesink", NULL); |
|
236 if (sink) { |
|
237 m_backend->logMessage("AudioOutput Using fake audio sink"); |
|
238 //without sync the sink will pull the pipeline as fast as the CPU allows |
|
239 g_object_set (G_OBJECT (sink), "sync", TRUE, (const char*)NULL); |
|
240 } |
|
241 } |
|
242 Q_ASSERT(sink); |
|
243 return sink; |
|
244 } |
|
245 |
|
246 AbstractRenderer *DeviceManager::createVideoRenderer(VideoWidget *parent) |
|
247 { |
|
248 #if !defined(QT_NO_OPENGL) && !defined(QT_OPENGL_ES) |
|
249 if (m_videoSinkWidget == "opengl") { |
|
250 return new GLRenderer(parent); |
|
251 } else |
|
252 #endif |
|
253 if (m_videoSinkWidget == "software") { |
|
254 return new WidgetRenderer(parent); |
|
255 } |
|
256 #ifndef Q_WS_QWS |
|
257 else if (m_videoSinkWidget == "xwindow") { |
|
258 return new X11Renderer(parent); |
|
259 } else { |
|
260 GstElementFactory *srcfactory = gst_element_factory_find("ximagesink"); |
|
261 if (srcfactory) { |
|
262 return new X11Renderer(parent); |
|
263 } |
|
264 } |
|
265 #endif |
|
266 return new WidgetRenderer(parent); |
|
267 } |
|
268 |
|
269 /* |
|
270 * Returns a positive device id or -1 if device |
|
271 * does not exist |
|
272 * |
|
273 * The gstId is typically in the format hw:1,0 |
|
274 */ |
|
275 int DeviceManager::deviceId(const QByteArray &gstId) const |
|
276 { |
|
277 for (int i = 0 ; i < m_audioDeviceList.size() ; ++i) { |
|
278 if (m_audioDeviceList[i].gstId == gstId) { |
|
279 return m_audioDeviceList[i].id; |
|
280 } |
|
281 } |
|
282 return -1; |
|
283 } |
|
284 |
|
285 /** |
|
286 * Get a human-readable description from a device id |
|
287 */ |
|
288 QByteArray DeviceManager::deviceDescription(int id) const |
|
289 { |
|
290 for (int i = 0 ; i < m_audioDeviceList.size() ; ++i) { |
|
291 if (m_audioDeviceList[i].id == id) { |
|
292 return m_audioDeviceList[i].description; |
|
293 } |
|
294 } |
|
295 return QByteArray(); |
|
296 } |
|
297 |
|
298 /** |
|
299 * Updates the current list of active devices |
|
300 */ |
|
301 void DeviceManager::updateDeviceList() |
|
302 { |
|
303 //fetch list of current devices |
|
304 GstElement *audioSink= createAudioSink(); |
|
305 |
|
306 QList<QByteArray> list; |
|
307 |
|
308 if (audioSink) { |
|
309 list = GstHelper::extractProperties(audioSink, "device"); |
|
310 list.prepend("default"); |
|
311 |
|
312 for (int i = 0 ; i < list.size() ; ++i) { |
|
313 QByteArray gstId = list.at(i); |
|
314 if (deviceId(gstId) == -1) { |
|
315 // This is a new device, add it |
|
316 m_audioDeviceList.append(AudioDevice(this, gstId)); |
|
317 emit deviceAdded(deviceId(gstId)); |
|
318 m_backend->logMessage(QString("Found new audio device %0").arg(QString::fromUtf8(gstId)), Backend::Debug, this); |
|
319 } |
|
320 } |
|
321 |
|
322 if (list.size() < m_audioDeviceList.size()) { |
|
323 //a device was removed |
|
324 for (int i = m_audioDeviceList.size() -1 ; i >= 0 ; --i) { |
|
325 QByteArray currId = m_audioDeviceList[i].gstId; |
|
326 bool found = false; |
|
327 for (int k = list.size() -1 ; k >= 0 ; --k) { |
|
328 if (currId == list[k]) { |
|
329 found = true; |
|
330 break; |
|
331 } |
|
332 } |
|
333 if (!found) { |
|
334 m_backend->logMessage(QString("Audio device lost %0").arg(QString::fromUtf8(currId)), Backend::Debug, this); |
|
335 emit deviceRemoved(deviceId(currId)); |
|
336 m_audioDeviceList.removeAt(i); |
|
337 } |
|
338 } |
|
339 } |
|
340 } |
|
341 |
|
342 gst_element_set_state (audioSink, GST_STATE_NULL); |
|
343 gst_object_unref (audioSink); |
|
344 } |
|
345 |
|
346 /** |
|
347 * Returns a list of hardware id usable by gstreamer [i.e hw:1,0] |
|
348 */ |
|
349 const QList<AudioDevice> DeviceManager::audioOutputDevices() const |
|
350 { |
|
351 return m_audioDeviceList; |
|
352 } |
|
353 |
|
354 } |
|
355 } |
|
356 |
|
357 QT_END_NAMESPACE |