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>
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.
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.
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.
22 #include "audiooutput.h"
24 #include <QtCore/QCoreApplication>
26 #include <sys/ioctl.h>
29 #include <phonon/pulsesupport.h>
30 #include "mediaobject.h"
34 #include "xineengine.h"
35 #include "xinethread.h"
36 #include "keepreference.h"
37 #include "audiodataoutput.h"
39 #include <xine/audio_out.h>
41 // the gcc 4.0 STL includes assert.h
49 AudioOutput::AudioOutput(QObject *parent)
50 : AbstractAudioOutput(new AudioOutputXT, parent)
55 AudioOutput::~AudioOutput()
57 //debug() << Q_FUNC_INFO ;
60 AudioOutputXT::~AudioOutputXT()
63 xine_close_audio_driver(m_xine, m_audioPort);
65 debug() << Q_FUNC_INFO << "----------------------------------------------- audio_port destroyed";
69 qreal AudioOutput::volume() const
74 int AudioOutput::outputDevice() const
76 return m_device.index();
79 void AudioOutput::setVolume(qreal newVolume)
83 int xinevolume = static_cast<int>(m_volume * 100);
84 if (xinevolume > 200) {
86 } else if (xinevolume < 0) {
90 upstreamEvent(new UpdateVolumeEvent(xinevolume));
91 emit volumeChanged(m_volume);
94 xine_audio_port_t *AudioOutputXT::audioPort() const
99 static QByteArray audioDriverFor(const QByteArray &driver)
101 if (driver == "alsa" || driver == "oss" || driver == "pulseaudio" || driver == "esd" ||
102 driver == "arts" || driver == "jack") {
108 static bool lookupConfigEntry(xine_t *xine, const char *key, xine_cfg_entry_t *entry, const char *driver)
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
114 xine_audio_port_t *port = xine_open_audio_driver(xine, driver, 0);
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
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";
129 xine_audio_port_t *AudioOutput::createPort(const AudioOutputDevice &deviceDesc)
132 xine_audio_port_t *port = 0;
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";
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";
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"));
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";
175 const QList<PhononDeviceAccess> &_deviceAccessList = deviceAccessList;
176 foreach (const PhononDeviceAccess &access, _deviceAccessList) {
177 const QByteArray &outputPlugin = audioDriverFor(access.first);
178 if (outputPlugin.isEmpty()) {
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")) {
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);
193 const int err = xine_config_lookup_entry(xt->m_xine, "audio.device.alsa_front_device",
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);
200 port = xine_open_audio_driver(xt->m_xine, "alsa", 0);
202 debug() << Q_FUNC_INFO << "use ALSA device: " << handle;
203 debug() << Q_FUNC_INFO << "----------------------------------------------- audio_port created";
206 } else if (outputPlugin == "pulseaudio") {
207 xine_cfg_entry_t deviceConfig;
208 if (!lookupConfigEntry(xt->m_xine, "audio.pulseaudio_device", &deviceConfig,
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);
218 port = xine_open_audio_driver(xt->m_xine, "pulseaudio", 0);
220 debug() << Q_FUNC_INFO << "use PulseAudio: " << handle;
221 debug() << Q_FUNC_INFO << "----------------------------------------------- audio_port created";
224 } else if (outputPlugin == "oss") {
225 xine_cfg_entry_t deviceConfig;
226 if (!lookupConfigEntry(xt->m_xine, "audio.device.oss_device_name", &deviceConfig,
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",
235 qWarning() << "cannot set the OSS device on Xine's OSS output plugin";
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');
249 deviceConfig.num_value = deviceNumber;
250 xine_config_update_entry(xt->m_xine, &deviceConfig);
252 port = xine_open_audio_driver(xt->m_xine, "oss", 0);
254 debug() << Q_FUNC_INFO << "use OSS device: " << handle;
255 debug() << Q_FUNC_INFO << "----------------------------------------------- audio_port created";
263 bool AudioOutput::setOutputDevice(int newDevice)
265 return setOutputDevice(AudioOutputDevice::fromIndex(newDevice));
268 bool AudioOutput::setOutputDevice(const AudioOutputDevice &newDevice)
272 // remember the choice until we have a xine_t
273 m_device = newDevice;
277 xine_audio_port_t *port = createPort(newDevice);
279 debug() << Q_FUNC_INFO << "new audio port is invalid";
283 KeepReference<> *keep = new KeepReference<>;
287 AudioOutputXT *newXt = new AudioOutputXT;
288 newXt->m_audioPort = port;
289 newXt->m_xine = xt->m_xine;
290 m_threadSafeObject = newXt;
292 m_device = newDevice;
293 SourceNode *src = source();
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));
303 AudioDataOutputXT *dataOutput = dynamic_cast<AudioDataOutputXT*>(m_source->threadSafeObject().data());
305 dataOutput->intercept(xt->m_audioPort);
310 void AudioOutput::xineEngineChanged()
314 xine_audio_port_t *port = createPort(m_device);
316 debug() << Q_FUNC_INFO << "stored audio port is invalid";
317 QMetaObject::invokeMethod(this, "audioDeviceFailed", Qt::QueuedConnection);
321 // our XT object is in a wirecall, better not delete it
323 Q_ASSERT(xt->m_audioPort == 0);
324 xt->m_audioPort = port;
327 AudioDataOutputXT *dataOutput = dynamic_cast<AudioDataOutputXT*>(m_source->threadSafeObject().data());
329 dataOutput->intercept(xt->m_audioPort);
333 void AudioOutput::aboutToChangeXineEngine()
336 if (xt->m_audioPort) {
337 AudioOutputXT *xt2 = new AudioOutputXT;
338 xt2->m_xine = xt->m_xine;
339 xt2->m_audioPort = xt->m_audioPort;
341 KeepReference<> *keep = new KeepReference<>;
342 keep->addObject(xt2);
347 void AudioOutput::downstreamEvent(Event *e)
350 QCoreApplication::sendEvent(this, e);
351 SinkNode::downstreamEvent(e);
354 void AudioOutputXT::rewireTo(SourceNodeXT *source)
356 if (!source->audioOutputPort()) {
360 xine_post_wire_audio_port(source->audioOutputPort(), m_audioPort);
362 SinkNodeXT::assert();
365 bool AudioOutput::event(QEvent *ev)
367 switch (ev->type()) {
368 case Event::AudioDeviceFailed:
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)) {
376 // we really need a different output device
377 QMetaObject::invokeMethod(this, "audioDeviceFailed", Qt::QueuedConnection);
381 return AbstractAudioOutput::event(ev);
385 void AudioOutput::graphChanged()
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) {
392 } else if (xinevolume < 0) {
395 upstreamEvent(new UpdateVolumeEvent(xinevolume));
398 }} //namespace Phonon::Xine
400 #include "audiooutput.moc"