xine: Initialise volume to 1.
[phonon:phonon.git] / xine / audiooutput.cpp
1 /*  This file is part of the KDE project
2     Copyright (C) 2006 Tim Beaulen <tbscope@gmail.com>
3     Copyright (C) 2006-2007 Matthias Kretz <kretz@kde.org>
4
5     This program is free software; you can redistribute it and/or
6     modify it under the terms of the GNU Library General Public
7     License as published by the Free Software Foundation; either
8     version 2 of the License, or (at your option) any later version.
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 GNU
13     Library General Public License for more details.
14
15     You should have received a copy of the GNU Library General Public License
16     along with this library; see the file COPYING.LIB.  If not, write to
17     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18     Boston, MA 02110-1301, USA.
19
20 */
21
22 #include "audiooutput.h"
23 #include <QVector>
24 #include <QtCore/QCoreApplication>
25
26 #include <sys/ioctl.h>
27 #include <iostream>
28 #include <QSet>
29 #include <phonon/pulsesupport.h>
30 #include "mediaobject.h"
31 #include "backend.h"
32 #include "events.h"
33 #include "wirecall.h"
34 #include "xineengine.h"
35 #include "xinethread.h"
36 #include "keepreference.h"
37 #include "audiodataoutput.h"
38
39 #include <xine/audio_out.h>
40
41 // the gcc 4.0 STL includes assert.h
42 #undef assert
43
44 namespace Phonon
45 {
46 namespace Xine
47 {
48
49 AudioOutput::AudioOutput(QObject *parent)
50     : AbstractAudioOutput(new AudioOutputXT, parent)
51     , m_volume(1.0)
52 {
53 }
54
55 AudioOutput::~AudioOutput()
56 {
57     //debug() << Q_FUNC_INFO ;
58 }
59
60 AudioOutputXT::~AudioOutputXT()
61 {
62     if (m_audioPort) {
63         xine_close_audio_driver(m_xine, m_audioPort);
64         m_audioPort = 0;
65         debug() << Q_FUNC_INFO << "----------------------------------------------- audio_port destroyed";
66     }
67 }
68
69 qreal AudioOutput::volume() const
70 {
71     return m_volume;
72 }
73
74 int AudioOutput::outputDevice() const
75 {
76     return m_device.index();
77 }
78
79 void AudioOutput::setVolume(qreal newVolume)
80 {
81     m_volume = newVolume;
82
83     int xinevolume = static_cast<int>(m_volume * 100);
84     if (xinevolume > 200) {
85         xinevolume = 200;
86     } else if (xinevolume < 0) {
87         xinevolume = 0;
88     }
89
90     upstreamEvent(new UpdateVolumeEvent(xinevolume));
91     emit volumeChanged(m_volume);
92 }
93
94 xine_audio_port_t *AudioOutputXT::audioPort() const
95 {
96     return m_audioPort;
97 }
98
99 static QByteArray audioDriverFor(const QByteArray &driver)
100 {
101     if (driver == "alsa" || driver == "oss" || driver == "pulseaudio" || driver == "esd" ||
102             driver == "arts" || driver == "jack") {
103         return driver;
104     }
105     return QByteArray();
106 }
107
108 static bool lookupConfigEntry(xine_t *xine, const char *key, xine_cfg_entry_t *entry, const char *driver)
109 {
110     if(!xine_config_lookup_entry(xine, key, entry)) {
111         // the config key is not registered yet - it is registered when the output
112         // plugin is opened. So we open the plugin and close it again, then we can set the
113         // setting. :(
114         xine_audio_port_t *port = xine_open_audio_driver(xine, driver, 0);
115         if (port) {
116             xine_close_audio_driver(xine, port);
117             // port == 0 does not have to be fatal, since it might be only the default device
118             // that cannot be opened
119         }
120         // now the config key should be registered
121         if(!xine_config_lookup_entry(xine, key, entry)) {
122             qWarning() << "cannot configure the device on Xine's" << driver << "output plugin";
123             return false;
124         }
125     }
126     return true;
127 }
128
129 xine_audio_port_t *AudioOutput::createPort(const AudioOutputDevice &deviceDesc)
130 {
131     K_XT(AudioOutput);
132     xine_audio_port_t *port = 0;
133
134     PulseSupport *pulse = PulseSupport::getInstance();
135     if (pulse->isActive()) {
136         // Here we trust that the PA plugin is setup correctly and we just want to use it.
137         const QByteArray &outputPlugin = "pulseaudio";
138         debug() << Q_FUNC_INFO << "PA Active: use output plugin:" << outputPlugin;
139         port = xine_open_audio_driver(xt->m_xine, outputPlugin.constData(), 0);
140         debug() << Q_FUNC_INFO << "----------------------------------------------- audio_port created";
141         return port;
142     }
143
144     if (!deviceDesc.isValid()) {
145         // use null output for invalid devices
146         port = xine_open_audio_driver(xt->m_xine, "none", 0);
147         debug() << Q_FUNC_INFO << "----------------------------------------------- null audio_port created";
148         return port;
149     }
150
151     typedef QPair<QByteArray, QString> PhononDeviceAccess;
152     QList<PhononDeviceAccess> deviceAccessList = deviceAccessListFor(deviceDesc);
153     if (deviceAccessList.isEmpty()) {
154         const QByteArray &outputPlugin = Backend::audioDriverFor(deviceDesc.index());
155         if (outputPlugin == "alsa") {
156             deviceAccessList << PhononDeviceAccess("alsa", QLatin1String("default"));
157             deviceAccessList << PhononDeviceAccess("alsa", QLatin1String("default:CARD=0"));
158             deviceAccessList << PhononDeviceAccess("alsa", QLatin1String("default:CARD=1"));
159             deviceAccessList << PhononDeviceAccess("alsa", QLatin1String("default:CARD=2"));
160             deviceAccessList << PhononDeviceAccess("alsa", QLatin1String("default:CARD=3"));
161             deviceAccessList << PhononDeviceAccess("alsa", QLatin1String("default:CARD=4"));
162         } else if (outputPlugin == "oss") {
163             deviceAccessList << PhononDeviceAccess("oss", QLatin1String("/dev/dsp"));
164             deviceAccessList << PhononDeviceAccess("oss", QLatin1String("/dev/dsp1"));
165             deviceAccessList << PhononDeviceAccess("oss", QLatin1String("/dev/dsp2"));
166             deviceAccessList << PhononDeviceAccess("oss", QLatin1String("/dev/dsp3"));
167             deviceAccessList << PhononDeviceAccess("oss", QLatin1String("/dev/dsp4"));
168         } else {
169             debug() << Q_FUNC_INFO << "use output plugin:" << outputPlugin;
170             port = xine_open_audio_driver(xt->m_xine, outputPlugin.constData(), 0);
171             debug() << Q_FUNC_INFO << "----------------------------------------------- audio_port created";
172             return port;
173         }
174     }
175     const QList<PhononDeviceAccess> &_deviceAccessList = deviceAccessList;
176     foreach (const PhononDeviceAccess &access, _deviceAccessList) {
177         const QByteArray &outputPlugin = audioDriverFor(access.first);
178         if (outputPlugin.isEmpty()) {
179             continue;
180         }
181         const QString &handle = access.second;
182         if (outputPlugin == "alsa") {
183             xine_cfg_entry_t deviceConfig;
184             if (!lookupConfigEntry(xt->m_xine, "audio.device.alsa_default_device",
185                         &deviceConfig, "alsa")) {
186                 continue;
187             }
188             Q_ASSERT(deviceConfig.type == XINE_CONFIG_TYPE_STRING);
189             QByteArray deviceStr = handle.toUtf8();
190             deviceConfig.str_value = deviceStr.data();
191             xine_config_update_entry(xt->m_xine, &deviceConfig);
192
193             const int err = xine_config_lookup_entry(xt->m_xine, "audio.device.alsa_front_device",
194                     &deviceConfig);
195             Q_ASSERT(err); Q_UNUSED(err);
196             Q_ASSERT(deviceConfig.type == XINE_CONFIG_TYPE_STRING);
197             deviceConfig.str_value = deviceStr.data();
198             xine_config_update_entry(xt->m_xine, &deviceConfig);
199
200             port = xine_open_audio_driver(xt->m_xine, "alsa", 0);
201             if (port) {
202                 debug() << Q_FUNC_INFO << "use ALSA device: " << handle;
203                 debug() << Q_FUNC_INFO << "----------------------------------------------- audio_port created";
204                 return port;
205             }
206         } else if (outputPlugin == "pulseaudio") {
207             xine_cfg_entry_t deviceConfig;
208             if (!lookupConfigEntry(xt->m_xine, "audio.pulseaudio_device", &deviceConfig,
209                     "pulseaudio")) {
210                 continue;
211             }
212             Q_ASSERT(deviceConfig.type == XINE_CONFIG_TYPE_STRING);
213             QByteArray deviceStr = handle.toUtf8();
214             deviceStr.replace('\n', ':');
215             deviceConfig.str_value = deviceStr.data();
216             xine_config_update_entry(xt->m_xine, &deviceConfig);
217
218             port = xine_open_audio_driver(xt->m_xine, "pulseaudio", 0);
219             if (port) {
220                 debug() << Q_FUNC_INFO << "use PulseAudio: " << handle;
221                 debug() << Q_FUNC_INFO << "----------------------------------------------- audio_port created";
222                 return port;
223             }
224         } else if (outputPlugin == "oss") {
225             xine_cfg_entry_t deviceConfig;
226             if (!lookupConfigEntry(xt->m_xine, "audio.device.oss_device_name", &deviceConfig,
227                         "oss")) {
228                 continue;
229             }
230             Q_ASSERT(deviceConfig.type == XINE_CONFIG_TYPE_ENUM);
231             deviceConfig.num_value = 0;
232             xine_config_update_entry(xt->m_xine, &deviceConfig);
233             if(!xine_config_lookup_entry(xt->m_xine, "audio.device.oss_device_number",
234                         &deviceConfig)) {
235                 qWarning() << "cannot set the OSS device on Xine's OSS output plugin";
236                 return 0;
237             }
238             Q_ASSERT(deviceConfig.type == XINE_CONFIG_TYPE_NUM);
239             const QByteArray &deviceStr = handle.toUtf8();
240             char lastChar = deviceStr[deviceStr.length() - 1];
241             int deviceNumber = -1;
242             if (lastChar >= '0' || lastChar <= '9') {
243                 deviceNumber = lastChar - '0';
244                 char lastChar = deviceStr[deviceStr.length() - 2];
245                 if (lastChar >= '0' || lastChar <= '9') {
246                     deviceNumber += 10 * (lastChar - '0');
247                 }
248             }
249             deviceConfig.num_value = deviceNumber;
250             xine_config_update_entry(xt->m_xine, &deviceConfig);
251
252             port = xine_open_audio_driver(xt->m_xine, "oss", 0);
253             if (port) {
254                 debug() << Q_FUNC_INFO << "use OSS device: " << handle;
255                 debug() << Q_FUNC_INFO << "----------------------------------------------- audio_port created";
256                 return port;
257             }
258         }
259     }
260     return port;
261 }
262
263 bool AudioOutput::setOutputDevice(int newDevice)
264 {
265     return setOutputDevice(AudioOutputDevice::fromIndex(newDevice));
266 }
267
268 bool AudioOutput::setOutputDevice(const AudioOutputDevice &newDevice)
269 {
270     K_XT(AudioOutput);
271     if (!xt->m_xine) {
272         // remember the choice until we have a xine_t
273         m_device = newDevice;
274         return true;
275     }
276
277     xine_audio_port_t *port = createPort(newDevice);
278     if (!port) {
279         debug() << Q_FUNC_INFO << "new audio port is invalid";
280         return false;
281     }
282
283     KeepReference<> *keep = new KeepReference<>;
284     keep->addObject(xt);
285     keep->ready();
286
287     AudioOutputXT *newXt = new AudioOutputXT;
288     newXt->m_audioPort = port;
289     newXt->m_xine = xt->m_xine;
290     m_threadSafeObject = newXt;
291
292     m_device = newDevice;
293     SourceNode *src = source();
294     if (src) {
295         QList<WireCall> wireCall;
296         QList<WireCall> unwireCall;
297         wireCall << WireCall(src, this);
298         unwireCall << WireCall(src, QExplicitlySharedDataPointer<SinkNodeXT>(xt));
299         QCoreApplication::postEvent(XineThread::instance(), new RewireEvent(wireCall, unwireCall));
300         graphChanged();
301     }
302
303     AudioDataOutputXT *dataOutput = dynamic_cast<AudioDataOutputXT*>(m_source->threadSafeObject().data());
304     if (dataOutput)
305         dataOutput->intercept(xt->m_audioPort);
306
307     return true;
308 }
309
310 void AudioOutput::xineEngineChanged()
311 {
312     K_XT(AudioOutput);
313     if (xt->m_xine) {
314         xine_audio_port_t *port = createPort(m_device);
315         if (!port) {
316             debug() << Q_FUNC_INFO << "stored audio port is invalid";
317             QMetaObject::invokeMethod(this, "audioDeviceFailed", Qt::QueuedConnection);
318             return;
319         }
320
321         // our XT object is in a wirecall, better not delete it
322
323         Q_ASSERT(xt->m_audioPort == 0);
324         xt->m_audioPort = port;
325
326
327         AudioDataOutputXT *dataOutput = dynamic_cast<AudioDataOutputXT*>(m_source->threadSafeObject().data());
328         if (dataOutput)
329             dataOutput->intercept(xt->m_audioPort);
330     }
331 }
332
333 void AudioOutput::aboutToChangeXineEngine()
334 {
335     K_XT(AudioOutput);
336     if (xt->m_audioPort) {
337         AudioOutputXT *xt2 = new AudioOutputXT;
338         xt2->m_xine = xt->m_xine;
339         xt2->m_audioPort = xt->m_audioPort;
340         xt->m_audioPort = 0;
341         KeepReference<> *keep = new KeepReference<>;
342         keep->addObject(xt2);
343         keep->ready();
344     }
345 }
346
347 void AudioOutput::downstreamEvent(Event *e)
348 {
349     Q_ASSERT(e);
350     QCoreApplication::sendEvent(this, e);
351     SinkNode::downstreamEvent(e);
352 }
353
354 void AudioOutputXT::rewireTo(SourceNodeXT *source)
355 {
356     if (!source->audioOutputPort()) {
357         return;
358     }
359     source->assert();
360     xine_post_wire_audio_port(source->audioOutputPort(), m_audioPort);
361     source->assert();
362     SinkNodeXT::assert();
363 }
364
365 bool AudioOutput::event(QEvent *ev)
366 {
367     switch (ev->type()) {
368     case Event::AudioDeviceFailed:
369         {
370             ev->accept();
371             // we don't know for sure which AudioPort failed. We also can't know from the
372             // information libxine makes available. So we have to just try the old device again
373             if (setOutputDevice(m_device)) {
374                 return true;
375             }
376             // we really need a different output device
377             QMetaObject::invokeMethod(this, "audioDeviceFailed", Qt::QueuedConnection);
378         }
379         return true;
380     default:
381         return AbstractAudioOutput::event(ev);
382     }
383 }
384
385 void AudioOutput::graphChanged()
386 {
387     debug() << Q_FUNC_INFO;
388     // we got connected to a new XineStream, it needs to know our m_volume
389     int xinevolume = static_cast<int>(m_volume * 100);
390     if (xinevolume > 200) {
391         xinevolume = 200;
392     } else if (xinevolume < 0) {
393         xinevolume = 0;
394     }
395     upstreamEvent(new UpdateVolumeEvent(xinevolume));
396 }
397
398 }} //namespace Phonon::Xine
399
400 #include "audiooutput.moc"
401 // vim: sw=4 ts=4