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 {
AudioOutput(Backend * backend,QObject * parent)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         m_audioBin = gst_bin_new (NULL);
46         gst_object_ref (GST_OBJECT (m_audioBin));
47         gst_object_sink (GST_OBJECT (m_audioBin));
48 
49         m_conv = gst_element_factory_make ("audioconvert", NULL);
50 
51         // Get category from parent
52         Phonon::Category category = Phonon::NoCategory;
53         if (Phonon::AudioOutput *audioOutput = qobject_cast<Phonon::AudioOutput *>(parent))
54             category = audioOutput->category();
55 
56         m_audioSink = m_backend->deviceManager()->createAudioSink(category);
57         m_volumeElement = gst_element_factory_make ("volume", NULL);
58         GstElement *queue = gst_element_factory_make ("queue", NULL);
59         GstElement *audioresample = gst_element_factory_make ("audioresample", NULL);
60 
61         if (queue && m_audioBin && m_conv && audioresample && m_audioSink && m_volumeElement) {
62             gst_bin_add_many (GST_BIN (m_audioBin), queue, m_conv, audioresample, m_volumeElement, m_audioSink, (const char*)NULL);
63 
64             if (gst_element_link_many (queue, m_conv, audioresample, m_volumeElement, m_audioSink, (const char*)NULL)) {
65                 // Add ghost sink for audiobin
66                 GstPad *audiopad = gst_element_get_pad (queue, "sink");
67                 gst_element_add_pad (m_audioBin, gst_ghost_pad_new ("sink", audiopad));
68                 gst_object_unref (audiopad);
69                 m_isValid = true; // Initialization ok, accept input
70             }
71         }
72     }
73 }
74 
mediaNodeEvent(const MediaNodeEvent * event)75 void AudioOutput::mediaNodeEvent(const MediaNodeEvent *event)
76 {
77     if (!m_audioBin)
78         return;
79 
80     switch (event->type()) {
81 
82     default:
83         break;
84     }
85 }
86 
87 
~AudioOutput()88 AudioOutput::~AudioOutput()
89 {
90     if (m_audioBin) {
91         gst_element_set_state (m_audioBin, GST_STATE_NULL);
92         gst_object_unref (m_audioBin);
93     }
94 }
95 
volume() const96 qreal AudioOutput::volume() const
97 {
98     return m_volumeLevel;
99 }
100 
outputDevice() const101 int AudioOutput::outputDevice() const
102 {
103     return m_device;
104 }
105 
setVolume(qreal newVolume)106 void AudioOutput::setVolume(qreal newVolume)
107 {
108     if (newVolume > 2.0 )
109         newVolume = 2.0;
110     else if (newVolume < 0.0)
111         newVolume = 0.0;
112 
113     if (newVolume == m_volumeLevel)
114         return;
115 
116     m_volumeLevel = newVolume;
117 
118     if (m_volumeElement) {
119         g_object_set(G_OBJECT(m_volumeElement), "volume", newVolume, (const char*)NULL);
120     }
121 
122     emit volumeChanged(newVolume);
123 }
124 
setOutputDevice(int newDevice)125 bool AudioOutput::setOutputDevice(int newDevice)
126 {
127     m_backend->logMessage(Q_FUNC_INFO + QString::number(newDevice), Backend::Info, this);
128 
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     if (m_audioSink &&  newDevice >= 0) {
140         // Save previous state
141         GstState oldState = GST_STATE(m_audioSink);
142         const QByteArray oldDeviceValue = GstHelper::property(m_audioSink, "device");
143         const QByteArray deviceId = m_backend->deviceManager()->gstId(newDevice);
144         m_device = newDevice;
145 
146         // We test if the device can be opened by checking if it can go from NULL to READY state
147         gst_element_set_state(m_audioSink, GST_STATE_NULL);
148         success = GstHelper::setProperty(m_audioSink, "device", deviceId);
149         if (success) {
150             success = (gst_element_set_state(m_audioSink, oldState) == GST_STATE_CHANGE_SUCCESS);
151         }
152         if (!success) { // Revert state
153             m_backend->logMessage(Q_FUNC_INFO +
154                                   QLatin1String(" Failed to change device ") +
155                                   deviceId, Backend::Info, this);
156 
157             GstHelper::setProperty(m_audioSink, "device", oldDeviceValue);
158             gst_element_set_state(m_audioSink, oldState);
159         } else {
160             m_backend->logMessage(Q_FUNC_INFO +
161                                   QLatin1String(" Successfully changed device ") +
162                                   deviceId, Backend::Info, this);
163         }
164 
165         // Note the stopped state should not really be necessary, but seems to be required to
166         // properly reset after changing the audio state
167         if (root()) {
168             QMetaObject::invokeMethod(root(), "setState", Qt::QueuedConnection, Q_ARG(State, StoppedState));
169             root()->resumeState();
170         }
171     }
172     return success;
173 }
174 
175 #if (PHONON_VERSION >= PHONON_VERSION_CHECK(4, 2, 0))
setOutputDevice(const AudioOutputDevice & newDevice)176 bool AudioOutput::setOutputDevice(const AudioOutputDevice &newDevice)
177 {
178     m_backend->logMessage(Q_FUNC_INFO, Backend::Info, this);
179     if (!m_audioSink || !newDevice.isValid()) {
180         return false;
181     }
182     const QVariant driver = newDevice.property("driver");
183     if (!driver.isValid()) {
184         return setOutputDevice(newDevice.index());
185     }
186     if (newDevice.index() == m_device) {
187         return true;
188     }
189 
190     if (root()) {
191         root()->saveState();
192         if (gst_element_set_state(root()->pipeline(), GST_STATE_READY) == GST_STATE_CHANGE_FAILURE)
193             return false;
194     }
195 
196     // Save previous state
197     const GstState oldState = GST_STATE(m_audioSink);
198     const QByteArray oldDeviceValue = GstHelper::property(m_audioSink, "device");
199 
200     const QByteArray sinkName = GstHelper::property(m_audioSink, "name");
201     if (sinkName == "alsasink" || sinkName == "alsasink2") {
202         if (driver.toByteArray() != "alsa") {
203             return false;
204         }
205     }
206 
207     const QVariant deviceIdsProperty = newDevice.property("deviceIds");
208     QStringList deviceIds;
209     if (deviceIdsProperty.type() == QVariant::StringList) {
210         deviceIds = deviceIdsProperty.toStringList();
211     } else if (deviceIdsProperty.type() == QVariant::String) {
212         deviceIds += deviceIdsProperty.toString();
213     }
214 
215     // We test if the device can be opened by checking if it can go from NULL to READY state
216     foreach (const QString &deviceId, deviceIds) {
217         gst_element_set_state(m_audioSink, GST_STATE_NULL);
218         if (GstHelper::setProperty(m_audioSink, "device", deviceId.toUtf8())) {
219             m_backend->logMessage(Q_FUNC_INFO + QLatin1String("setProperty(device,") +
220                                   deviceId + QLatin1String(") succeeded"), Backend::Info, this);
221             if (gst_element_set_state(m_audioSink, oldState) == GST_STATE_CHANGE_SUCCESS) {
222                 m_backend->logMessage(Q_FUNC_INFO + QLatin1String("go to old state on device") +
223                                       deviceId + QLatin1String(" succeeded"), Backend::Info, this);
224                 m_device = newDevice.index();
225                 if (root()) {
226                     QMetaObject::invokeMethod(root(), "setState", Qt::QueuedConnection, Q_ARG(State, StoppedState));
227                     root()->resumeState();
228                 }
229                 return true;
230             } else {
231                 m_backend->logMessage(Q_FUNC_INFO + QLatin1String("go to old state on device") +
232                                       deviceId + QLatin1String(" failed"), Backend::Info, this);
233             }
234         } else {
235             m_backend->logMessage(Q_FUNC_INFO + QLatin1String("setProperty(device,") +
236                                   deviceId + QLatin1String(") failed"), Backend::Info, this);
237         }
238     }
239     // Revert state
240     GstHelper::setProperty(m_audioSink, "device", oldDeviceValue);
241     gst_element_set_state(m_audioSink, oldState);
242 
243     if (root()) {
244         QMetaObject::invokeMethod(root(), "setState", Qt::QueuedConnection, Q_ARG(State, StoppedState));
245         root()->resumeState();
246     }
247 
248     return false;
249 }
250 #endif
251 
252 }
253 } //namespace Phonon::Gstreamer
254 
255 QT_END_NAMESPACE
256 #include "moc_audiooutput.cpp"
257