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