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 "audiooutput.h"
20 #include "backend.h"
21 #include "debug.h"
22 #include "devicemanager.h"
23 #include "mediaobject.h"
24 #include "gsthelper.h"
25 #include "phonon-config-gstreamer.h"
26 #include <phonon/audiooutput.h>
27 #include <phonon/pulsesupport.h>
28 
29 #include <QtCore/QStringBuilder>
30 
31 #include <gst/gst.h>
32 #include <gst/gstbin.h>
33 #include <gst/gstghostpad.h>
34 #include <gst/gstutils.h>
35 
36 namespace Phonon
37 {
38 namespace Gstreamer
39 {
AudioOutput(Backend * backend,QObject * parent)40 AudioOutput::AudioOutput(Backend *backend, QObject *parent)
41         : QObject(parent)
42         , MediaNode(backend, AudioSink)
43         , m_volumeLevel(1.0)
44         , m_device(0) // ### get from backend
45         , m_volumeElement(0)
46         , m_audioBin(0)
47         , m_audioSink(0)
48         , m_conv(0)
49 {
50     static int count = 0;
51     m_name = "AudioOutput" + QString::number(count++);
52 
53     m_audioBin = gst_bin_new(NULL);
54     gst_object_ref_sink(GST_OBJECT (m_audioBin));
55 
56     m_conv = gst_element_factory_make("audioconvert", NULL);
57 
58     // Get category from parent
59     Phonon::Category category = Phonon::NoCategory;
60     if (Phonon::AudioOutput *audioOutput = qobject_cast<Phonon::AudioOutput *>(parent))
61         category = audioOutput->category();
62 
63     m_audioSink = m_backend->deviceManager()->createAudioSink(category);
64     gst_object_ref_sink(m_audioSink);
65     m_volumeElement = gst_element_factory_make("volume", NULL);
66     GstElement *queue = gst_element_factory_make("queue", NULL);
67     GstElement *audioresample = gst_element_factory_make("audioresample", NULL);
68 
69     if (queue && m_audioBin && m_conv && audioresample && m_audioSink && m_volumeElement) {
70         gst_bin_add_many(GST_BIN(m_audioBin), queue, m_conv,
71                          audioresample, m_volumeElement, m_audioSink, NULL);
72 
73         if (gst_element_link_many(queue, m_conv, audioresample, m_volumeElement,
74                                   m_audioSink, NULL)) {
75             // Add ghost sink for audiobin
76             GstPad *audiopad = gst_element_get_static_pad(queue, "sink");
77             gst_element_add_pad (m_audioBin, gst_ghost_pad_new("sink", audiopad));
78             gst_object_unref(audiopad);
79             m_isValid = true; // Initialization ok, accept input
80         }
81     }
82 }
83 
~AudioOutput()84 AudioOutput::~AudioOutput()
85 {
86     if (m_audioBin) {
87         gst_element_set_state(m_audioBin, GST_STATE_NULL);
88         gst_object_unref(m_audioBin);
89         m_audioBin = 0;
90     }
91     if (m_audioSink) {
92         gst_element_set_state(m_audioSink, GST_STATE_NULL);
93         gst_object_unref(m_audioSink);
94         m_audioSink = 0;
95     }
96 }
97 
volume() const98 qreal AudioOutput::volume() const
99 {
100     return m_volumeLevel;
101 }
102 
outputDevice() const103 int AudioOutput::outputDevice() const
104 {
105     return m_device;
106 }
107 
setVolume(qreal newVolume)108 void AudioOutput::setVolume(qreal newVolume)
109 {
110     if (newVolume > 2.0) {
111         newVolume = 2.0;
112     } else if (newVolume < 0.0) {
113         newVolume = 0.0;
114     }
115 
116     if (newVolume == m_volumeLevel) {
117         return;
118     }
119 
120     m_volumeLevel = newVolume;
121 
122     if (m_volumeElement) {
123         g_object_set(G_OBJECT(m_volumeElement), "volume", newVolume, NULL);
124     }
125 
126     emit volumeChanged(newVolume);
127 }
128 
129 /*
130  * Reimp
131  */
setOutputDevice(int newDevice)132 bool AudioOutput::setOutputDevice(int newDevice)
133 {
134     const AudioOutputDevice device = AudioOutputDevice::fromIndex(newDevice);
135     if (!device.isValid()) {
136         error() << Q_FUNC_INFO << "Unable to find the output device with index" << newDevice;
137         return false;
138     }
139     return setOutputDevice(device);
140 }
141 
142 #if (PHONON_VERSION >= PHONON_VERSION_CHECK(4, 2, 0))
setOutputDevice(const AudioOutputDevice & newDevice)143 bool AudioOutput::setOutputDevice(const AudioOutputDevice &newDevice)
144 {
145     debug() << Q_FUNC_INFO;
146     if (!m_audioSink || !newDevice.isValid()) {
147         return false;
148     }
149 
150     const QVariant dalProperty = newDevice.property("deviceAccessList");
151     if (!dalProperty.isValid()) {
152         return false;
153     }
154     const DeviceAccessList deviceAccessList = dalProperty.value<DeviceAccessList>();
155     if (deviceAccessList.isEmpty()) {
156         return false;
157     }
158 
159     if (newDevice.index() == m_device) {
160         return true;
161     }
162 
163     if (root()) {
164         root()->saveState();
165         if (root()->pipeline()->setState(GST_STATE_READY) == GST_STATE_CHANGE_FAILURE) {
166             return false;
167         }
168     }
169 
170     // Save previous state
171     const GstState oldState = GST_STATE(m_audioSink);
172     const QByteArray oldDeviceValue = GstHelper::property(m_audioSink, "device");
173 
174     foreach (const DeviceAccess &deviceAccess, deviceAccessList) {
175         if (setOutputDevice(deviceAccess.first, deviceAccess.second, oldState)) {
176             m_device = newDevice.index();
177             return true;
178         }
179     }
180 
181     // Revert state
182     GstHelper::setProperty(m_audioSink, "device", oldDeviceValue);
183     gst_element_set_state(m_audioSink, oldState);
184 
185     if (root()) {
186         QMetaObject::invokeMethod(root(), "setState",
187                                   Qt::QueuedConnection, Q_ARG(State, StoppedState));
188         root()->resumeState();
189     }
190 
191     return false;
192 }
193 
setOutputDevice(const QByteArray & driver,const QString & deviceId,const GstState oldState)194 bool AudioOutput::setOutputDevice(const QByteArray &driver, const QString &deviceId, const GstState oldState)
195 {
196     const QByteArray sinkName = GstHelper::property(m_audioSink, "name");
197     if (sinkName == QByteArray("alsasink")) {
198         if (driver != QByteArray("alsa")) {
199             return false;
200         }
201     }
202 
203     // We test if the device can be opened by checking if it can go from NULL to READY state
204     gst_element_set_state(m_audioSink, GST_STATE_NULL);
205     if (GstHelper::setProperty(m_audioSink, "device", deviceId.toUtf8())) {
206         debug() << Q_FUNC_INFO << "setProperty( device," << deviceId << ") succeeded";
207         if (gst_element_set_state(m_audioSink, oldState) == GST_STATE_CHANGE_SUCCESS) {
208             debug() << Q_FUNC_INFO << "go to old state on device" << deviceId << "succeeded";
209             if (root()) {
210                 QMetaObject::invokeMethod(root(), "setState",
211                                             Qt::QueuedConnection,
212                                             Q_ARG(State, StoppedState));
213                 root()->resumeState();
214             }
215             return true;
216         } else {
217             error() << Q_FUNC_INFO << "go to old state on device" << deviceId << "failed";
218         }
219     } else {
220         error() << Q_FUNC_INFO << "setProperty( device," << deviceId << ") failed";
221     }
222 
223     return false;
224 }
225 #endif
226 
227 #if (PHONON_VERSION >= PHONON_VERSION_CHECK(4, 6, 50))
setStreamUuid(QString uuid)228 void AudioOutput::setStreamUuid(QString uuid)
229 {
230     m_streamUuid = uuid;
231 #warning this really needs a check for pulsesink as well
232     if (g_object_class_find_property(G_OBJECT_GET_CLASS(m_audioSink), "stream-properties")) {
233         const QHash<QString, QString> streamProperties = PulseSupport::getInstance()->streamProperties(uuid);
234         GstStructure *properties = gst_structure_new_empty("props");
235         QHashIterator<QString, QString> it(streamProperties);
236         while (it.hasNext()) {
237             it.next();
238             gst_structure_set(properties,
239                               it.key().toUtf8().constData(), G_TYPE_STRING, it.value().toUtf8().constData(),
240                               NULL);
241         }
242 
243         Q_ASSERT(properties);
244         g_object_set (m_audioSink, "stream-properties", properties, NULL);
245         gst_structure_free(properties);
246     }
247 }
248 #endif
249 
250 }
251 } //namespace Phonon::Gstreamer
252 #include "moc_audiooutput.cpp"
253