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