mirror of
https://bitbucket.org/smil3y/kde-extraapps.git
synced 2025-02-24 02:42:52 +00:00
330 lines
11 KiB
C++
330 lines
11 KiB
C++
/*
|
|
* KMix -- KDE's full featured mini mixer
|
|
*
|
|
* Copyright 2006-2007 Christian Esken <esken@kde.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This program 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
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this program; if not, write to the Free
|
|
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "mixer_backend.h"
|
|
|
|
#include <klocale.h>
|
|
|
|
// for the "ERR_" declartions, #include mixer.h
|
|
#include "core/mixer.h"
|
|
#include "core/ControlManager.h"
|
|
|
|
#include <QTimer>
|
|
|
|
#define POLL_RATE_SLOW 1500
|
|
#define POLL_RATE_FAST 50
|
|
|
|
|
|
#include "mixer_backend_i18n.cpp"
|
|
|
|
Mixer_Backend::Mixer_Backend(Mixer *mixer, int device) :
|
|
m_devnum (device) , m_isOpen(false), m_recommendedMaster(), _mixer(mixer), _pollingTimer(0), _cardInstance(1)
|
|
|
|
{
|
|
// In all cases create a QTimer. We will use it once as a singleShot(), even if something smart
|
|
// like ::select() is possible (as in ALSA). And force to do an update.
|
|
_readSetFromHWforceUpdate = true;
|
|
_pollingTimer = new QTimer(); // will be started on open() and stopped on close()
|
|
connect( _pollingTimer, SIGNAL(timeout()), this, SLOT(readSetFromHW()), Qt::QueuedConnection);
|
|
|
|
}
|
|
|
|
void Mixer_Backend::closeCommon()
|
|
{
|
|
freeMixDevices();
|
|
}
|
|
|
|
int Mixer_Backend::close()
|
|
{
|
|
kDebug() << "Implicit close on " << this << ". Please instead call closeCommon() and close() explicitly (in concrete Backend destructor)";
|
|
// ^^^ Background. before the destructor runs, the C++ runtime changes the virtual pointers to point back
|
|
// to the common base class. So what actually runs is not run Mixer_ALSA::close(), but this method.
|
|
//
|
|
// Comment: IMO this is totally stupid and insane behavior of C++, because you cannot simply cannot call
|
|
// the overwritten (cleanup) methods in the destructor.
|
|
return 0;
|
|
}
|
|
|
|
Mixer_Backend::~Mixer_Backend()
|
|
{
|
|
unregisterCard(this->getName());
|
|
if (!m_mixDevices.isEmpty())
|
|
{
|
|
kDebug() << "Implicit close on " << this << ". Please instead call closeCommon() and close() explicitly (in concrete Backend destructor)";
|
|
}
|
|
delete _pollingTimer;
|
|
}
|
|
|
|
void Mixer_Backend::freeMixDevices()
|
|
{
|
|
foreach (std::shared_ptr<MixDevice> md, m_mixDevices)
|
|
md->close();
|
|
|
|
m_mixDevices.clear();
|
|
}
|
|
|
|
bool Mixer_Backend::openIfValid()
|
|
{
|
|
int ret = open();
|
|
if (ret == 0 && (m_mixDevices.count() > 0 || _mixer->isDynamic()))
|
|
{
|
|
// Hint: _id is probably not yet perfectly set, as it requires the value from open() and an external
|
|
// counter. Thus we start the Timer while _id is not properly set. But it will be done immediately
|
|
// by the caller of this method.
|
|
// Future directions: Do the counter calculation in the backend. It really belongs there, as it is part of
|
|
// the PK calculation. Probably provide a standard implementation in Mixer_Backend itself. Also the
|
|
// key should be an own class, like: MixerKey(QString backend, QString baseId, int cardInstance)
|
|
if (needsPolling())
|
|
{
|
|
_pollingTimer->start(POLL_RATE_FAST);
|
|
}
|
|
else
|
|
{
|
|
// The initial state must be read manually
|
|
QTimer::singleShot( POLL_RATE_FAST, this, SLOT(readSetFromHW()));
|
|
}
|
|
return true; // could be opened
|
|
}
|
|
else
|
|
{
|
|
//shutdown();
|
|
return false; // could not open
|
|
}
|
|
}
|
|
|
|
bool Mixer_Backend::isOpen() {
|
|
return m_isOpen;
|
|
}
|
|
|
|
/**
|
|
* Queries the backend driver whether there are new changes in any of the controls.
|
|
* If you cannot find out for a backend, return "true" - this is also the default implementation.
|
|
* @return true, if there are changes. Otherwise false is returned.
|
|
*/
|
|
bool Mixer_Backend::prepareUpdateFromHW() {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* The name of the Mixer this backend represents.
|
|
* Often it is just a name/id for the kernel. so name and id are usually identical. Virtual/abstracting backends are
|
|
* different, as they represent some distinct function like "Application streams" or "Capture Devices". Also backends
|
|
* that do not have names might can to set ID and name different like i18n("SUN Audio") and "SUNAudio".
|
|
*/
|
|
QString Mixer_Backend::getName() const
|
|
{
|
|
return m_mixerName;
|
|
}
|
|
|
|
/**
|
|
* The id of the Mixer this backend represents. The default implementation simply returns the name.
|
|
* Often it is just a name/id for the kernel. so name and id are usually identical. See also #Mixer_Backend::getName().
|
|
* You must override this method if you want to set ID different from name.
|
|
*/
|
|
QString Mixer_Backend::getId() const
|
|
{
|
|
return m_mixerName; // Backwards compatibility.
|
|
}
|
|
|
|
/**
|
|
* After calling this, readSetFromHW() will do a complete update. This will
|
|
* trigger emitting the appropriate signals like controlChanged().
|
|
*
|
|
* This method is useful, if you need to get a "refresh signal" - used at:
|
|
* 1) Start of KMix - so that we can be sure an initial signal is emitted
|
|
* 2) When reconstructing any MixerWidget (e.g. DockIcon after applying preferences)
|
|
*/
|
|
void Mixer_Backend::readSetFromHWforceUpdate() const {
|
|
_readSetFromHWforceUpdate = true;
|
|
}
|
|
|
|
|
|
/**
|
|
* You can call this to retrieve the freshest information from the mixer HW.
|
|
* This method is also called regularly by the mixer timer.
|
|
*/
|
|
void Mixer_Backend::readSetFromHW()
|
|
{
|
|
bool updated = prepareUpdateFromHW();
|
|
if ( (! updated) && (! _readSetFromHWforceUpdate) ) {
|
|
// Some drivers (ALSA) are smart. We don't need to run the following
|
|
// time-consuming update loop if there was no change
|
|
kDebug(67100) << "Mixer::readSetFromHW(): smart-update-tick";
|
|
return;
|
|
}
|
|
|
|
_readSetFromHWforceUpdate = false;
|
|
|
|
int ret = Mixer::OK_UNCHANGED;
|
|
|
|
foreach (std::shared_ptr<MixDevice> md, m_mixDevices )
|
|
{
|
|
//bool debugMe = (md->id() == "PCM:0" );
|
|
bool debugMe = false;
|
|
if (debugMe) kDebug() << "Old PCM:0 playback state" << md->isMuted()
|
|
<< ", vol=" << md->playbackVolume().getAvgVolumePercent(Volume::MALL);
|
|
|
|
int retLoop = readVolumeFromHW( md->id(), md );
|
|
if (debugMe) kDebug() << "New PCM:0 playback state" << md->isMuted()
|
|
<< ", vol=" << md->playbackVolume().getAvgVolumePercent(Volume::MALL);
|
|
if (md->isEnum() )
|
|
{
|
|
/*
|
|
* This could be reworked:
|
|
* Plan: Read everything (incuding enum's) in readVolumeFromHW().
|
|
* readVolumeFromHW() should then be renamed to readHW().
|
|
*/
|
|
md->setEnumId( enumIdHW(md->id()) );
|
|
}
|
|
|
|
// Transition the outer return value with the value from this loop iteration
|
|
if ( retLoop == Mixer::OK && ret == Mixer::OK_UNCHANGED )
|
|
{
|
|
// Unchanged => OK (Changed)
|
|
ret = Mixer::OK;
|
|
}
|
|
else if ( retLoop != Mixer::OK && retLoop != Mixer::OK_UNCHANGED )
|
|
{
|
|
// If current ret from loop in not OK, then transition to that: ret (Something) => retLoop (Error)
|
|
ret = retLoop;
|
|
}
|
|
}
|
|
|
|
if ( ret == Mixer::OK )
|
|
{
|
|
// We explicitly exclude Mixer::OK_UNCHANGED and Mixer::ERROR_READ
|
|
if ( needsPolling() )
|
|
{
|
|
// Upgrade polling frequency temporarily to be more smoooooth
|
|
_pollingTimer->setInterval(POLL_RATE_FAST);
|
|
QTime fastPollingEndsAt = QTime::currentTime ();
|
|
fastPollingEndsAt = fastPollingEndsAt.addSecs(5);
|
|
_fastPollingEndsAt = fastPollingEndsAt;
|
|
//_fastPollingEndsAt = fastPollingEndsAt;
|
|
kDebug() << "Start fast polling from " << QTime::currentTime() <<"until " << _fastPollingEndsAt;
|
|
}
|
|
|
|
ControlManager::instance().announce(_mixer->id(), ControlChangeType::Volume, QString("Mixer.fromHW"));
|
|
}
|
|
|
|
else
|
|
{
|
|
// This code path is entered on Mixer::OK_UNCHANGED and ERROR
|
|
bool fastPollingEndsNow = (!_fastPollingEndsAt.isNull()) && _fastPollingEndsAt < QTime::currentTime ();
|
|
if ( fastPollingEndsNow )
|
|
{
|
|
kDebug() << "End fast polling";
|
|
_fastPollingEndsAt = QTime(); // NULL time
|
|
_pollingTimer->setInterval(POLL_RATE_SLOW);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return the MixDevice, that would qualify best as MasterDevice. The default is to return the
|
|
* first device in the device list. Backends can override this (i.e. the ALSA Backend does so).
|
|
* The users preference is NOT returned by this method - see the Mixer class for that.
|
|
*/
|
|
std::shared_ptr<MixDevice> Mixer_Backend::recommendedMaster()
|
|
{
|
|
if ( m_recommendedMaster )
|
|
{
|
|
// Backend has set a recommended master. Thats fine. Using it.
|
|
return m_recommendedMaster;
|
|
}
|
|
else if ( ! m_mixDevices.isEmpty() )
|
|
{
|
|
// Backend has NOT set a recommended master. Evil backend
|
|
// => lets help out, using the first device (if exists)
|
|
return m_mixDevices.at(0);
|
|
}
|
|
else
|
|
{
|
|
if ( !_mixer->isDynamic())
|
|
// This should never ever happen, as KMix does NOT accept soundcards without controls
|
|
kError(67100) << "Mixer_Backend::recommendedMaster(): returning invalid master. This is a bug in KMix. Please file a bug report stating how you produced this.";
|
|
}
|
|
|
|
// If we reach this code path, then obiously m_recommendedMaster == 0 (see above)
|
|
return m_recommendedMaster;
|
|
|
|
}
|
|
|
|
/**
|
|
* Sets the ID of the currently selected Enum entry.
|
|
* This is a dummy implementation - if the Mixer backend
|
|
* wants to support it, it must implement the driver specific
|
|
* code in its subclass (see Mixer_ALSA.cpp for an example).
|
|
*/
|
|
void Mixer_Backend::setEnumIdHW(const QString& , unsigned int) {
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Return the ID of the currently selected Enum entry.
|
|
* This is a dummy implementation - if the Mixer backend
|
|
* wants to support it, it must implement the driver specific
|
|
* code in its subclass (see Mixer_ALSA.cpp for an example).
|
|
*/
|
|
unsigned int Mixer_Backend::enumIdHW(const QString& ) {
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Move the stream to a new destination
|
|
*/
|
|
bool Mixer_Backend::moveStream( const QString& id, const QString& destId ) {
|
|
Q_UNUSED(id);
|
|
Q_UNUSED(destId);
|
|
return false;
|
|
}
|
|
|
|
|
|
QString Mixer_Backend::errorText(int mixer_error)
|
|
{
|
|
QString l_s_errmsg;
|
|
switch (mixer_error)
|
|
{
|
|
case Mixer::ERR_PERM:
|
|
l_s_errmsg = i18n("kmix:You do not have permission to access the mixer device.\n" \
|
|
"Please check your operating systems manual to allow the access.");
|
|
break;
|
|
case Mixer::ERR_WRITE:
|
|
l_s_errmsg = i18n("kmix: Could not write to mixer.");
|
|
break;
|
|
case Mixer::ERR_READ:
|
|
l_s_errmsg = i18n("kmix: Could not read from mixer.");
|
|
break;
|
|
case Mixer::ERR_OPEN:
|
|
l_s_errmsg = i18n("kmix: Mixer cannot be found.\n" \
|
|
"Please check that the soundcard is installed and that\n" \
|
|
"the soundcard driver is loaded.\n");
|
|
break;
|
|
default:
|
|
l_s_errmsg = i18n("kmix: Unknown error. Please report how you produced this error.");
|
|
break;
|
|
}
|
|
return l_s_errmsg;
|
|
}
|
|
|
|
|
|
|
|
|
|
#include "moc_mixer_backend.cpp"
|