|
1 /* This file is part of the KDE project. |
|
2 |
|
3 Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). |
|
4 Copyright (C) 2008 Matthias Kretz <kretz@kde.org> |
|
5 |
|
6 This library is free software: you can redistribute it and/or modify |
|
7 it under the terms of the GNU Lesser General Public License as published by |
|
8 the Free Software Foundation, either version 2.1 or 3 of the License. |
|
9 |
|
10 This library is distributed in the hope that it will be useful, |
|
11 but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
13 GNU Lesser General Public License for more details. |
|
14 |
|
15 You should have received a copy of the GNU Lesser General Public License |
|
16 along with this library. If not, see <http://www.gnu.org/licenses/>. |
|
17 */ |
|
18 |
|
19 #include "common.h" |
|
20 #include "audiooutput.h" |
|
21 #include "backend.h" |
|
22 #include "mediaobject.h" |
|
23 #include "gsthelper.h" |
|
24 #include <phonon/audiooutput.h> |
|
25 |
|
26 QT_BEGIN_NAMESPACE |
|
27 |
|
28 namespace Phonon |
|
29 { |
|
30 namespace Gstreamer |
|
31 { |
|
32 AudioOutput::AudioOutput(Backend *backend, QObject *parent) |
|
33 : QObject(parent) |
|
34 , MediaNode(backend, AudioSink) |
|
35 , m_volumeLevel(1.0) |
|
36 , m_device(0) // ### get from backend |
|
37 , m_volumeElement(0) |
|
38 , m_audioBin(0) |
|
39 , m_audioSink(0) |
|
40 , m_conv(0) |
|
41 { |
|
42 static int count = 0; |
|
43 m_name = "AudioOutput" + QString::number(count++); |
|
44 if (m_backend->isValid()) { |
|
45 g_set_application_name(qApp->applicationName().toUtf8()); |
|
46 m_audioBin = gst_bin_new (NULL); |
|
47 gst_object_ref (GST_OBJECT (m_audioBin)); |
|
48 gst_object_sink (GST_OBJECT (m_audioBin)); |
|
49 |
|
50 m_conv = gst_element_factory_make ("audioconvert", NULL); |
|
51 |
|
52 // Get category from parent |
|
53 Phonon::Category category = Phonon::NoCategory; |
|
54 if (Phonon::AudioOutput *audioOutput = qobject_cast<Phonon::AudioOutput *>(parent)) |
|
55 category = audioOutput->category(); |
|
56 |
|
57 m_audioSink = m_backend->deviceManager()->createAudioSink(category); |
|
58 m_volumeElement = gst_element_factory_make ("volume", NULL); |
|
59 GstElement *queue = gst_element_factory_make ("queue", NULL); |
|
60 GstElement *audioresample = gst_element_factory_make ("audioresample", NULL); |
|
61 |
|
62 if (queue && m_audioBin && m_conv && audioresample && m_audioSink && m_volumeElement) { |
|
63 gst_bin_add_many (GST_BIN (m_audioBin), queue, m_conv, audioresample, m_volumeElement, m_audioSink, (const char*)NULL); |
|
64 |
|
65 if (gst_element_link_many (queue, m_conv, audioresample, m_volumeElement, m_audioSink, (const char*)NULL)) { |
|
66 // Add ghost sink for audiobin |
|
67 GstPad *audiopad = gst_element_get_pad (queue, "sink"); |
|
68 gst_element_add_pad (m_audioBin, gst_ghost_pad_new ("sink", audiopad)); |
|
69 gst_object_unref (audiopad); |
|
70 m_isValid = true; // Initialization ok, accept input |
|
71 } |
|
72 } |
|
73 } |
|
74 } |
|
75 |
|
76 void AudioOutput::mediaNodeEvent(const MediaNodeEvent *event) |
|
77 { |
|
78 if (!m_audioBin) |
|
79 return; |
|
80 |
|
81 switch (event->type()) { |
|
82 |
|
83 default: |
|
84 break; |
|
85 } |
|
86 } |
|
87 |
|
88 |
|
89 AudioOutput::~AudioOutput() |
|
90 { |
|
91 if (m_audioBin) { |
|
92 gst_element_set_state (m_audioBin, GST_STATE_NULL); |
|
93 gst_object_unref (m_audioBin); |
|
94 } |
|
95 } |
|
96 |
|
97 qreal AudioOutput::volume() const |
|
98 { |
|
99 return m_volumeLevel; |
|
100 } |
|
101 |
|
102 int AudioOutput::outputDevice() const |
|
103 { |
|
104 return m_device; |
|
105 } |
|
106 |
|
107 void AudioOutput::setVolume(qreal newVolume) |
|
108 { |
|
109 if (newVolume > 2.0 ) |
|
110 newVolume = 2.0; |
|
111 else if (newVolume < 0.0) |
|
112 newVolume = 0.0; |
|
113 |
|
114 if (newVolume == m_volumeLevel) |
|
115 return; |
|
116 |
|
117 m_volumeLevel = newVolume; |
|
118 |
|
119 if (m_volumeElement) { |
|
120 g_object_set(G_OBJECT(m_volumeElement), "volume", newVolume, (const char*)NULL); |
|
121 } |
|
122 |
|
123 emit volumeChanged(newVolume); |
|
124 } |
|
125 |
|
126 bool AudioOutput::setOutputDevice(int newDevice) |
|
127 { |
|
128 m_backend->logMessage(Q_FUNC_INFO + QString::number(newDevice), Backend::Info, this); |
|
129 if (newDevice == m_device) |
|
130 return true; |
|
131 |
|
132 if (root()) { |
|
133 root()->saveState(); |
|
134 if (gst_element_set_state(root()->pipeline(), GST_STATE_READY) == GST_STATE_CHANGE_FAILURE) |
|
135 return false; |
|
136 } |
|
137 |
|
138 bool success = false; |
|
139 const QList<AudioDevice> deviceList = m_backend->deviceManager()->audioOutputDevices(); |
|
140 if (m_audioSink && newDevice >= 0 && newDevice < deviceList.size()) { |
|
141 // Save previous state |
|
142 GstState oldState = GST_STATE(m_audioSink); |
|
143 const QByteArray oldDeviceValue = GstHelper::property(m_audioSink, "device"); |
|
144 const QByteArray deviceId = deviceList.at(newDevice).gstId; |
|
145 m_device = newDevice; |
|
146 |
|
147 // We test if the device can be opened by checking if it can go from NULL to READY state |
|
148 gst_element_set_state(m_audioSink, GST_STATE_NULL); |
|
149 success = GstHelper::setProperty(m_audioSink, "device", deviceId); |
|
150 if (success) { |
|
151 success = (gst_element_set_state(m_audioSink, oldState) == GST_STATE_CHANGE_SUCCESS); |
|
152 } |
|
153 if (!success) { // Revert state |
|
154 m_backend->logMessage(Q_FUNC_INFO + |
|
155 QLatin1String(" Failed to change device ") + |
|
156 deviceId, Backend::Info, this); |
|
157 |
|
158 GstHelper::setProperty(m_audioSink, "device", oldDeviceValue); |
|
159 gst_element_set_state(m_audioSink, oldState); |
|
160 } else { |
|
161 m_backend->logMessage(Q_FUNC_INFO + |
|
162 QLatin1String(" Successfully changed device ") + |
|
163 deviceId, Backend::Info, this); |
|
164 } |
|
165 |
|
166 // Note the stopped state should not really be neccessary, but seems to be required to |
|
167 // properly reset after changing the audio state |
|
168 if (root()) { |
|
169 QMetaObject::invokeMethod(root(), "setState", Qt::QueuedConnection, Q_ARG(State, StoppedState)); |
|
170 root()->resumeState(); |
|
171 } |
|
172 } |
|
173 return success; |
|
174 } |
|
175 |
|
176 #if (PHONON_VERSION >= PHONON_VERSION_CHECK(4, 2, 0)) |
|
177 bool AudioOutput::setOutputDevice(const AudioOutputDevice &newDevice) |
|
178 { |
|
179 m_backend->logMessage(Q_FUNC_INFO, Backend::Info, this); |
|
180 if (!m_audioSink || !newDevice.isValid()) { |
|
181 return false; |
|
182 } |
|
183 const QVariant driver = newDevice.property("driver"); |
|
184 if (!driver.isValid()) { |
|
185 return setOutputDevice(newDevice.index()); |
|
186 } |
|
187 if (newDevice.index() == m_device) { |
|
188 return true; |
|
189 } |
|
190 |
|
191 if (root()) { |
|
192 root()->saveState(); |
|
193 if (gst_element_set_state(root()->pipeline(), GST_STATE_READY) == GST_STATE_CHANGE_FAILURE) |
|
194 return false; |
|
195 } |
|
196 |
|
197 // Save previous state |
|
198 const GstState oldState = GST_STATE(m_audioSink); |
|
199 const QByteArray oldDeviceValue = GstHelper::property(m_audioSink, "device"); |
|
200 |
|
201 const QByteArray sinkName = GstHelper::property(m_audioSink, "name"); |
|
202 if (sinkName == "alsasink" || sinkName == "alsasink2") { |
|
203 if (driver.toByteArray() != "alsa") { |
|
204 return false; |
|
205 } |
|
206 } |
|
207 |
|
208 const QVariant deviceIdsProperty = newDevice.property("deviceIds"); |
|
209 QStringList deviceIds; |
|
210 if (deviceIdsProperty.type() == QVariant::StringList) { |
|
211 deviceIds = deviceIdsProperty.toStringList(); |
|
212 } else if (deviceIdsProperty.type() == QVariant::String) { |
|
213 deviceIds += deviceIdsProperty.toString(); |
|
214 } |
|
215 |
|
216 // We test if the device can be opened by checking if it can go from NULL to READY state |
|
217 foreach (const QString &deviceId, deviceIds) { |
|
218 gst_element_set_state(m_audioSink, GST_STATE_NULL); |
|
219 if (GstHelper::setProperty(m_audioSink, "device", deviceId.toUtf8())) { |
|
220 m_backend->logMessage(Q_FUNC_INFO + QLatin1String("setProperty(device,") + |
|
221 deviceId + QLatin1String(") succeeded"), Backend::Info, this); |
|
222 if (gst_element_set_state(m_audioSink, oldState) == GST_STATE_CHANGE_SUCCESS) { |
|
223 m_backend->logMessage(Q_FUNC_INFO + QLatin1String("go to old state on device") + |
|
224 deviceId + QLatin1String(" succeeded"), Backend::Info, this); |
|
225 m_device = newDevice.index(); |
|
226 if (root()) { |
|
227 QMetaObject::invokeMethod(root(), "setState", Qt::QueuedConnection, Q_ARG(State, StoppedState)); |
|
228 root()->resumeState(); |
|
229 } |
|
230 return true; |
|
231 } else { |
|
232 m_backend->logMessage(Q_FUNC_INFO + QLatin1String("go to old state on device") + |
|
233 deviceId + QLatin1String(" failed"), Backend::Info, this); |
|
234 } |
|
235 } else { |
|
236 m_backend->logMessage(Q_FUNC_INFO + QLatin1String("setProperty(device,") + |
|
237 deviceId + QLatin1String(") failed"), Backend::Info, this); |
|
238 } |
|
239 } |
|
240 // Revert state |
|
241 GstHelper::setProperty(m_audioSink, "device", oldDeviceValue); |
|
242 gst_element_set_state(m_audioSink, oldState); |
|
243 |
|
244 if (root()) { |
|
245 QMetaObject::invokeMethod(root(), "setState", Qt::QueuedConnection, Q_ARG(State, StoppedState)); |
|
246 root()->resumeState(); |
|
247 } |
|
248 |
|
249 return false; |
|
250 } |
|
251 #endif |
|
252 |
|
253 } |
|
254 } //namespace Phonon::Gstreamer |
|
255 |
|
256 QT_END_NAMESPACE |
|
257 #include "moc_audiooutput.cpp" |