You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lxqt-panel-packaging/plugin-volume/alsaengine.cpp

223 lines
6.9 KiB

/* BEGIN_COMMON_COPYRIGHT_HEADER
* (c)LGPL2+
*
* LXDE-Qt - a lightweight, Qt based, desktop toolset
* http://razor-qt.org
*
* Copyright: 2012 Razor team
* Authors:
* Johannes Zellner <webmaster@nebulon.de>
*
* This program or library is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*
* END_COMMON_COPYRIGHT_HEADER */
#include "alsaengine.h"
#include "alsadevice.h"
#include <QMetaType>
#include <QSocketNotifier>
#include <QtDebug>
AlsaEngine *AlsaEngine::m_instance = 0;
static int alsa_elem_event_callback(snd_mixer_elem_t *elem, unsigned int mask)
{
AlsaEngine *engine = AlsaEngine::instance();
if (engine)
engine->updateDevice(engine->getDeviceByAlsaElem(elem));
return 0;
}
static int alsa_mixer_event_callback(snd_mixer_t *mixer, unsigned int mask, snd_mixer_elem_t *elem)
{
return 0;
}
AlsaEngine::AlsaEngine(QObject *parent) :
AudioEngine(parent)
{
discoverDevices();
m_instance = this;
}
AlsaEngine *AlsaEngine::instance()
{
return m_instance;
}
int AlsaEngine::volumeMax(AudioDevice *device) const
{
AlsaDevice * alsa_dev = qobject_cast<AlsaDevice *>(device);
Q_ASSERT(alsa_dev);
return alsa_dev->volumeMax();
}
AlsaDevice *AlsaEngine::getDeviceByAlsaElem(snd_mixer_elem_t *elem) const
{
foreach (AudioDevice *device, m_sinks) {
AlsaDevice *dev = qobject_cast<AlsaDevice*>(device);
if (!dev || !dev->element())
continue;
if (dev->element() == elem)
return dev;
}
return 0;
}
void AlsaEngine::commitDeviceVolume(AudioDevice *device)
{
AlsaDevice *dev = qobject_cast<AlsaDevice*>(device);
if (!dev || !dev->element())
return;
long value = dev->volumeMin() + qRound(static_cast<double>(dev->volume()) / 100.0 * (dev->volumeMax() - dev->volumeMin()));
snd_mixer_selem_set_playback_volume_all(dev->element(), value);
}
void AlsaEngine::setMute(AudioDevice *device, bool state)
{
AlsaDevice *dev = qobject_cast<AlsaDevice*>(device);
if (!dev || !dev->element())
return;
if (snd_mixer_selem_has_playback_switch(dev->element()))
snd_mixer_selem_set_playback_switch_all(dev->element(), (int)!state);
else if (state)
dev->setVolume(0);
}
void AlsaEngine::updateDevice(AlsaDevice *device)
{
if (!device)
return;
long value;
snd_mixer_selem_get_playback_volume(device->element(), (snd_mixer_selem_channel_id_t)0, &value);
// qDebug() << "updateDevice:" << device->name() << value;
device->setVolumeNoCommit(qRound((static_cast<double>(value - device->volumeMin()) * 100.0) / (device->volumeMax() - device->volumeMin())));
if (snd_mixer_selem_has_playback_switch(device->element())) {
int mute;
snd_mixer_selem_get_playback_switch(device->element(), (snd_mixer_selem_channel_id_t)0, &mute);
device->setMuteNoCommit(!(bool)mute);
}
}
void AlsaEngine::driveAlsaEventHandling(int fd)
{
snd_mixer_handle_events(m_mixerMap.value(fd));
}
void AlsaEngine::discoverDevices()
{
int error;
int cardNum = -1;
const int BUFF_SIZE = 64;
while (1) {
if ((error = snd_card_next(&cardNum)) < 0) {
qWarning("Can't get the next card number: %s\n", snd_strerror(error));
break;
}
if (cardNum < 0)
break;
char str[BUFF_SIZE];
const size_t n = snprintf(str, sizeof(str), "hw:%i", cardNum);
if (BUFF_SIZE <= n) {
qWarning("AlsaEngine::discoverDevices: Buffer too small\n");
continue;
}
snd_ctl_t *cardHandle;
if ((error = snd_ctl_open(&cardHandle, str, 0)) < 0) {
qWarning("Can't open card %i: %s\n", cardNum, snd_strerror(error));
continue;
}
snd_ctl_card_info_t *cardInfo;
snd_ctl_card_info_alloca(&cardInfo);
QString cardName = QString::fromLatin1(snd_ctl_card_info_get_name(cardInfo));
if (cardName.isEmpty())
cardName = QString::fromLatin1(str);
if ((error = snd_ctl_card_info(cardHandle, cardInfo)) < 0) {
qWarning("Can't get info for card %i: %s\n", cardNum, snd_strerror(error));
} else {
// setup mixer and iterate over channels
snd_mixer_t *mixer = 0;
snd_mixer_open(&mixer, 0);
snd_mixer_attach(mixer, str);
snd_mixer_selem_register(mixer, NULL, NULL);
snd_mixer_load(mixer);
// setup event handler for mixer
snd_mixer_set_callback(mixer, alsa_mixer_event_callback);
// setup eventloop handling
struct pollfd pfd;
if (snd_mixer_poll_descriptors(mixer, &pfd, 1)) {
QSocketNotifier *notifier = new QSocketNotifier(pfd.fd, QSocketNotifier::Read, this);
connect(notifier, SIGNAL(activated(int)), this, SLOT(driveAlsaEventHandling(int)));
m_mixerMap.insert(pfd.fd, mixer);
}
snd_mixer_elem_t *mixerElem = 0;
mixerElem = snd_mixer_first_elem(mixer);
while (mixerElem) {
// check if we have a Sink or Source
if (snd_mixer_selem_has_playback_volume(mixerElem)) {
AlsaDevice *dev = new AlsaDevice(Sink, this, this);
dev->setName(QString::fromLatin1(snd_mixer_selem_get_name(mixerElem)));
dev->setIndex(cardNum);
dev->setDescription(cardName + " - " + dev->name());
// set alsa specific members
dev->setCardName(QString::fromLatin1(str));
dev->setMixer(mixer);
dev->setElement(mixerElem);
// get & store the range
long min, max;
snd_mixer_selem_get_playback_volume_range(mixerElem, &min, &max);
dev->setVolumeMinMax(min, max);
updateDevice(dev);
// register event callback
snd_mixer_elem_set_callback(mixerElem, alsa_elem_event_callback);
m_sinks.append(dev);
}
mixerElem = snd_mixer_elem_next(mixerElem);
}
}
snd_ctl_close(cardHandle);
}
snd_config_update_free_global();
}