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.
847 lines
27 KiB
847 lines
27 KiB
/*
|
|
* KMix -- KDE's full featured mini mixer
|
|
* Alsa 0.9x and 1.0 - Based on original alsamixer code
|
|
* from alsa-project ( www/alsa-project.org )
|
|
*
|
|
*
|
|
* Copyright (C) 2002 Helio Chissini de Castro <helio@conectiva.com.br>
|
|
* 2004 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.
|
|
*/
|
|
|
|
// STD Headers
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <iostream>
|
|
#include <assert.h>
|
|
#include <qsocketnotifier.h>
|
|
|
|
extern "C"
|
|
{
|
|
#include <alsa/asoundlib.h>
|
|
}
|
|
|
|
// KDE Headers
|
|
#include <klocale.h>
|
|
#include <kdebug.h>
|
|
|
|
// Local Headers
|
|
#include "mixer_alsa.h"
|
|
//#include "mixer.h"
|
|
#include "volume.h"
|
|
// #define if you want MUCH debugging output
|
|
//#define ALSA_SWITCH_DEBUG
|
|
//#define KMIX_ALSA_VOLUME_DEBUG
|
|
|
|
Mixer_Backend*
|
|
ALSA_getMixer( int device )
|
|
{
|
|
Mixer_Backend *l_mixer;
|
|
l_mixer = new Mixer_ALSA( device );
|
|
return l_mixer;
|
|
}
|
|
|
|
Mixer_ALSA::Mixer_ALSA( int device ) : Mixer_Backend( device )
|
|
{
|
|
m_fds = 0;
|
|
m_sns = 0;
|
|
_handle = 0;
|
|
_initialUpdate = true;
|
|
}
|
|
|
|
Mixer_ALSA::~Mixer_ALSA()
|
|
{
|
|
close();
|
|
}
|
|
|
|
int
|
|
Mixer_ALSA::identify( snd_mixer_selem_id_t *sid )
|
|
{
|
|
QString name = snd_mixer_selem_id_get_name( sid );
|
|
|
|
if ( name == "Master" ) return MixDevice::VOLUME;
|
|
if ( name == "Capture" ) return MixDevice::RECMONITOR;
|
|
if ( name == "Master Mono" ) return MixDevice::VOLUME;
|
|
if ( name == "PC Speaker" ) return MixDevice::VOLUME;
|
|
if ( name == "Music" || name == "Synth" || name == "FM" ) return MixDevice::MIDI;
|
|
if ( name.find( "Headphone", 0, false ) != -1 ) return MixDevice::HEADPHONE;
|
|
if ( name == "Bass" ) return MixDevice::BASS;
|
|
if ( name == "Treble" ) return MixDevice::TREBLE;
|
|
if ( name == "CD" ) return MixDevice::CD;
|
|
if ( name == "Video" ) return MixDevice::VIDEO;
|
|
if ( name == "PCM" || name == "Wave" ) return MixDevice::AUDIO;
|
|
if ( name == "Surround" ) return MixDevice::SURROUND_BACK;
|
|
if ( name == "Center" ) return MixDevice::SURROUND_CENTERFRONT;
|
|
if ( name.find( "ac97", 0, false ) != -1 ) return MixDevice::AC97;
|
|
if ( name.find( "coaxial", 0, false ) != -1 ) return MixDevice::DIGITAL;
|
|
if ( name.find( "optical", 0, false ) != -1 ) return MixDevice::DIGITAL;
|
|
if ( name.find( "IEC958", 0, false ) != -1 ) return MixDevice::DIGITAL;
|
|
if ( name.find( "Mic" ) != -1 ) return MixDevice::MICROPHONE;
|
|
if ( name.find( "LFE" ) != -1 ) return MixDevice::SURROUND_LFE;
|
|
if ( name.find( "Monitor" ) != -1 ) return MixDevice::RECMONITOR;
|
|
if ( name.find( "3D", 0, false ) != -1 ) return MixDevice::SURROUND; // Should be probably some own icon
|
|
|
|
return MixDevice::EXTERNAL;
|
|
}
|
|
|
|
int
|
|
Mixer_ALSA::open()
|
|
{
|
|
bool virginOpen = m_mixDevices.isEmpty();
|
|
bool validDevice = false;
|
|
bool masterChosen = false;
|
|
int err;
|
|
|
|
snd_ctl_t *ctl_handle;
|
|
snd_ctl_card_info_t *hw_info;
|
|
snd_ctl_card_info_alloca(&hw_info);
|
|
|
|
snd_mixer_elem_t *elem;
|
|
snd_mixer_selem_id_t *sid;
|
|
snd_mixer_selem_id_alloca( &sid );
|
|
|
|
// Card information
|
|
if ((unsigned)m_devnum > 31) m_devnum = -1;
|
|
devName = m_devnum == -1 ? "default" : QString("hw:%1").arg(m_devnum);
|
|
|
|
QString probeMessage;
|
|
|
|
if (virginOpen)
|
|
probeMessage += "Trying ALSA Device '" + devName + "': ";
|
|
|
|
if ( ( err = snd_ctl_open ( &ctl_handle, devName.latin1(), 0 ) ) < 0 )
|
|
{
|
|
kdDebug(67100) << probeMessage << "not found: snd_ctl_open err=" << snd_strerror(err) << endl;
|
|
//_stateMessage = errorText( Mixer::ERR_NODEV );
|
|
return Mixer::ERR_OPEN;
|
|
}
|
|
|
|
if ( ( err = snd_ctl_card_info ( ctl_handle, hw_info ) ) < 0 )
|
|
{
|
|
kdDebug(67100) << probeMessage << "not found: snd_ctl_card_info err=" << snd_strerror(err) << endl;
|
|
//_stateMessage = errorText( Mixer::ERR_READ );
|
|
snd_ctl_close( ctl_handle );
|
|
return Mixer::ERR_READ;
|
|
}
|
|
|
|
// Device and mixer names
|
|
const char* mixer_card_name = snd_ctl_card_info_get_name( hw_info );
|
|
//mixer_device_name = snd_ctl_card_info_get_mixername( hw_info );
|
|
// Copy the name of kmix mixer from card name (mixername is rumoured to be not that good)
|
|
m_mixerName = mixer_card_name;
|
|
if (m_devnum == -1) m_devnum = snd_card_get_index(snd_ctl_card_info_get_id(hw_info));
|
|
if (m_devnum < 0) m_devnum = -1;
|
|
|
|
snd_ctl_close( ctl_handle );
|
|
|
|
/* open mixer device */
|
|
|
|
//kdDebug(67100) << "IN Mixer_ALSA snd_mixer_open()" << endl;
|
|
if ( ( err = snd_mixer_open ( &_handle, 0 ) ) < 0 )
|
|
{
|
|
kdDebug(67100) << probeMessage << "not found: snd_mixer_open err=" << snd_strerror(err) << endl;
|
|
//errormsg( Mixer::ERR_NODEV );
|
|
_handle = 0;
|
|
return Mixer::ERR_NODEV; // if we cannot open the mixer, we have no devices
|
|
}
|
|
//kdDebug(67100) << "OUT Mixer_ALSA snd_mixer_open()" << endl;
|
|
|
|
if ( ( err = snd_mixer_attach ( _handle, devName.latin1() ) ) < 0 )
|
|
{
|
|
kdDebug(67100) << probeMessage << "not found: snd_mixer_attach err=" << snd_strerror(err) << endl;
|
|
//errormsg( Mixer::ERR_PERM );
|
|
return Mixer::ERR_OPEN;
|
|
}
|
|
|
|
if ( ( err = snd_mixer_selem_register ( _handle, NULL, NULL ) ) < 0 )
|
|
{
|
|
kdDebug(67100) << probeMessage << "not found: snd_mixer_selem_register err=" << snd_strerror(err) << endl;
|
|
//errormsg( Mixer::ERR_READ );
|
|
return Mixer::ERR_READ;
|
|
}
|
|
|
|
if ( ( err = snd_mixer_load ( _handle ) ) < 0 )
|
|
{
|
|
kdDebug(67100) << probeMessage << "not found: snd_mixer_load err=" << snd_strerror(err) << endl;
|
|
//errormsg( Mixer::ERR_READ );
|
|
close();
|
|
return Mixer::ERR_READ;
|
|
}
|
|
|
|
kdDebug(67100) << probeMessage << "found" << endl;
|
|
|
|
unsigned int mixerIdx = 0;
|
|
for ( elem = snd_mixer_first_elem( _handle ); elem; elem = snd_mixer_elem_next( elem ), mixerIdx++ )
|
|
{
|
|
// If element is not active, just skip
|
|
if ( ! snd_mixer_selem_is_active ( elem ) ) {
|
|
// ...but we still want to insert a null value into our mixer element
|
|
// list so that the list indexes match up.
|
|
mixer_elem_list.append( 0 );
|
|
mixer_sid_list.append( 0 );
|
|
continue;
|
|
}
|
|
|
|
|
|
sid = (snd_mixer_selem_id_t*)malloc(snd_mixer_selem_id_sizeof()); // I believe *we* must malloc it for ourself
|
|
snd_mixer_selem_get_id( elem, sid );
|
|
|
|
bool canRecord = false;
|
|
bool canMute = false;
|
|
bool canCapture = false;
|
|
long maxVolumePlay= 0, minVolumePlay= 0;
|
|
long maxVolumeRec = 0, minVolumeRec = 0;
|
|
validDevice = true;
|
|
|
|
snd_mixer_selem_get_playback_volume_range( elem, &minVolumePlay, &maxVolumePlay );
|
|
snd_mixer_selem_get_capture_volume_range( elem, &minVolumeRec , &maxVolumeRec );
|
|
// New mix device
|
|
MixDevice::ChannelType ct = (MixDevice::ChannelType)identify( sid );
|
|
/*
|
|
if (!masterChosen && ct==MixDevice::VOLUME) {
|
|
// Determine a nicer MasterVolume
|
|
m_masterDevice = mixerIdx;
|
|
masterChosen = true;
|
|
}
|
|
*/
|
|
if( virginOpen )
|
|
{
|
|
MixDevice::DeviceCategory cc = MixDevice::UNDEFINED;
|
|
|
|
//kdDebug() << "--- Loop: name=" << snd_mixer_selem_id_get_name( sid ) << " , mixerIdx=" << mixerIdx << "------------" << endl;
|
|
|
|
Volume* volPlay = 0, *volCapture = 0;
|
|
QPtrList<QString> enumList;
|
|
if ( snd_mixer_selem_is_enumerated(elem) ) {
|
|
cc = MixDevice::ENUM;
|
|
volPlay = new Volume(); // Dummy, unused
|
|
volCapture = new Volume();
|
|
mixer_elem_list.append( elem );
|
|
mixer_sid_list.append( sid );
|
|
|
|
// --- get Enum names START ---
|
|
int numEnumitems = snd_mixer_selem_get_enum_items(elem);
|
|
if ( numEnumitems > 0 ) {
|
|
// OK. no error
|
|
for (int iEnum = 0; iEnum<numEnumitems; iEnum++ ) {
|
|
char buffer[100];
|
|
int ret = snd_mixer_selem_get_enum_item_name(elem, iEnum, 99, buffer);
|
|
if ( ret == 0 ) {
|
|
QString* enumName = new QString(buffer);
|
|
//enumName->append(buffer);
|
|
enumList.append( enumName);
|
|
} // enumName could be read succesfully
|
|
} // for all enum items of this device
|
|
} // no error in reading enum list
|
|
else {
|
|
// 0 items or Error code => ignore this entry
|
|
}
|
|
// --- get Enum names END ---
|
|
} // is an enum
|
|
|
|
else {
|
|
Volume::ChannelMask chn = Volume::MNONE;
|
|
Volume::ChannelMask chnTmp;
|
|
if ( snd_mixer_selem_has_playback_volume(elem) ) {
|
|
//kdDebug(67100) << "has_playback_volume()" << endl;
|
|
chnTmp = snd_mixer_selem_is_playback_mono ( elem )
|
|
? Volume::MLEFT : (Volume::ChannelMask)(Volume::MLEFT | Volume::MRIGHT);
|
|
chn = (Volume::ChannelMask) (chn | chnTmp);
|
|
cc = MixDevice::SLIDER;
|
|
volPlay = new Volume( chn, maxVolumePlay, minVolumePlay );
|
|
} else {
|
|
volPlay = new Volume();
|
|
}
|
|
if ( snd_mixer_selem_has_capture_volume(elem) ) {
|
|
//kdDebug(67100) << "has_capture_volume()" << endl;
|
|
chnTmp = snd_mixer_selem_is_capture_mono( elem )
|
|
? Volume::MLEFT : (Volume::ChannelMask)(Volume::MLEFT | Volume::MRIGHT );
|
|
chn = (Volume::ChannelMask) (chn | chnTmp);
|
|
cc = MixDevice::SLIDER;
|
|
canCapture = true;
|
|
volCapture = new Volume( chn, maxVolumeRec, minVolumeRec, true );
|
|
} else {
|
|
volCapture = new Volume();
|
|
}
|
|
|
|
/* Create Volume object. If there is no volume on this device,
|
|
* it will be created with maxVolume == 0 && minVolume == 0 */
|
|
mixer_elem_list.append( elem );
|
|
mixer_sid_list.append( sid );
|
|
|
|
if ( snd_mixer_selem_has_playback_switch ( elem ) ) {
|
|
//kdDebug(67100) << "has_playback_switch()" << endl;
|
|
canMute = true;
|
|
}
|
|
if ( snd_mixer_selem_has_capture_switch ( elem ) ) {
|
|
//kdDebug(67100) << "has_capture_switch()" << endl;
|
|
canRecord = true;
|
|
}
|
|
if ( snd_mixer_selem_has_common_switch ( elem ) ) {
|
|
//kdDebug(67100) << "has_common_switch()" << endl;
|
|
canMute = true;
|
|
canRecord = true;
|
|
}
|
|
|
|
if ( /*snd_mixer_selem_has_common_switch ( elem ) || */
|
|
cc == MixDevice::UNDEFINED )
|
|
{
|
|
// Everything unknown is handled as switch
|
|
cc = MixDevice::SWITCH;
|
|
}
|
|
} // is ordinary mixer element (NOT an enum)
|
|
|
|
MixDevice* md = new MixDevice( mixerIdx,
|
|
*volPlay,
|
|
canRecord,
|
|
canMute,
|
|
snd_mixer_selem_id_get_name( sid ),
|
|
ct,
|
|
cc );
|
|
|
|
m_mixDevices.append( md );
|
|
|
|
|
|
if (!masterChosen && ct==MixDevice::VOLUME) {
|
|
// Determine a nicer MasterVolume
|
|
m_recommendedMaster = md;
|
|
masterChosen = true;
|
|
}
|
|
|
|
if ( canCapture && !canRecord ) {
|
|
MixDevice *mdCapture =
|
|
new MixDevice( mixerIdx,
|
|
*volCapture,
|
|
true,
|
|
canMute,
|
|
snd_mixer_selem_id_get_name( sid ),
|
|
ct,
|
|
cc );
|
|
m_mixDevices.append( mdCapture );
|
|
}
|
|
|
|
if ( enumList.count() > 0 ) {
|
|
int maxEnumId= enumList.count();
|
|
QPtrList<QString>& enumValuesRef = md->enumValues(); // retrieve a ref
|
|
for (int i=0; i<maxEnumId; i++ ) {
|
|
// we have an enum. Lets set the names of the enum items in the MixDevice
|
|
// the enum names are assumed to be static!
|
|
enumValuesRef.append(enumList.at(i) );
|
|
}
|
|
}
|
|
//kdDebug(67100) << "ALSA create MDW, vol= " << *vol << endl;
|
|
delete volPlay;
|
|
delete volCapture;
|
|
} // virginOpen
|
|
else
|
|
{
|
|
MixDevice* md;
|
|
bool found = false;
|
|
for ( md = m_mixDevices.first(); md != 0; md = m_mixDevices.next() ) {
|
|
if ( md->num() == mixerIdx ) {
|
|
found = true;
|
|
writeVolumeToHW( mixerIdx, md->getVolume() );
|
|
}
|
|
}
|
|
if( !found )
|
|
{
|
|
return Mixer::ERR_INCOMPATIBLESET;
|
|
}
|
|
} // !virginOpen
|
|
} // for all elems
|
|
|
|
/**************************************************************************************
|
|
// If no devices are supported by this soundcard, return "NO Devices"
|
|
It is VERY important to return THIS error code, so that the caller knows, that the
|
|
the device exists.
|
|
This is used for scanning for existing soundcard devices, see MixerToolBox::initMixer().
|
|
***************************************************************************************/
|
|
if ( !validDevice )
|
|
{
|
|
return Mixer::ERR_NODEV;
|
|
}
|
|
|
|
// Copy the name of kmix mixer from card name
|
|
// Real name of mixer is not too good
|
|
m_mixerName = mixer_card_name;
|
|
|
|
// return with success
|
|
m_isOpen = true;
|
|
|
|
/* setup for select on stdin and the mixer fd */
|
|
if ((m_count = snd_mixer_poll_descriptors_count(_handle)) < 0) {
|
|
kdDebug(67100) << "Mixer_ALSA::poll() , snd_mixer_poll_descriptors_count() err=" << m_count << "\n";
|
|
return Mixer::ERR_OPEN;
|
|
}
|
|
|
|
//kdDebug(67100) << "Mixer_ALSA::prepareUpdate() 2\n";
|
|
|
|
m_fds = (struct pollfd*)calloc(m_count, sizeof(struct pollfd));
|
|
if (m_fds == NULL) {
|
|
kdDebug(67100) << "Mixer_ALSA::poll() , calloc() = null" << "\n";
|
|
return Mixer::ERR_OPEN;
|
|
}
|
|
|
|
m_fds->events = POLLIN;
|
|
if ((err = snd_mixer_poll_descriptors(_handle, m_fds, m_count)) < 0) {
|
|
kdDebug(67100) << "Mixer_ALSA::poll() , snd_mixer_poll_descriptors_count() err=" << err << "\n";
|
|
return Mixer::ERR_OPEN;
|
|
}
|
|
if (err != m_count) {
|
|
kdDebug(67100) << "Mixer_ALSA::poll() , snd_mixer_poll_descriptors_count() err=" << err << " m_count=" << m_count << "\n";
|
|
return Mixer::ERR_OPEN;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void Mixer_ALSA::prepareSignalling( Mixer *mixer )
|
|
{
|
|
assert( !m_sns );
|
|
|
|
m_sns = new QSocketNotifier*[m_count];
|
|
for ( int i = 0; i < m_count; ++i )
|
|
{
|
|
kdDebug() << "socket " << i << endl;
|
|
m_sns[i] = new QSocketNotifier(m_fds[i].fd, QSocketNotifier::Read);
|
|
mixer->connect(m_sns[i], SIGNAL(activated(int)), mixer, SLOT(readSetFromHW()));
|
|
}
|
|
}
|
|
|
|
void Mixer_ALSA::removeSignalling()
|
|
{
|
|
if ( m_fds )
|
|
free( m_fds );
|
|
m_fds = 0;
|
|
|
|
if ( m_sns )
|
|
{
|
|
for ( int i = 0; i < m_count; i++ )
|
|
delete m_sns[i];
|
|
delete [] m_sns;
|
|
m_sns = 0;
|
|
}
|
|
}
|
|
|
|
int
|
|
Mixer_ALSA::close()
|
|
{
|
|
int ret=0;
|
|
m_isOpen = false;
|
|
if ( _handle != 0 )
|
|
{
|
|
//kdDebug(67100) << "IN Mixer_ALSA::close()" << endl;
|
|
snd_mixer_free ( _handle );
|
|
if ( ( ret = snd_mixer_detach ( _handle, devName.latin1() ) ) < 0 )
|
|
{
|
|
kdDebug(67100) << "snd_mixer_detach err=" << snd_strerror(ret) << endl;
|
|
}
|
|
int ret2 = 0;
|
|
if ( ( ret2 = snd_mixer_close ( _handle ) ) < 0 )
|
|
{
|
|
kdDebug(67100) << "snd_mixer_close err=" << snd_strerror(ret2) << endl;
|
|
if ( ret == 0 ) ret = ret2; // no error before => use current error code
|
|
}
|
|
|
|
_handle = 0;
|
|
//kdDebug(67100) << "OUT Mixer_ALSA::close()" << endl;
|
|
|
|
}
|
|
|
|
mixer_elem_list.clear();
|
|
mixer_sid_list.clear();
|
|
m_mixDevices.clear();
|
|
|
|
removeSignalling();
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
snd_mixer_elem_t* Mixer_ALSA::getMixerElem(int devnum) {
|
|
snd_mixer_elem_t* elem = 0;
|
|
if ( ! m_isOpen ) return elem; // unplugging guard
|
|
|
|
if ( int( mixer_sid_list.count() ) > devnum ) {
|
|
snd_mixer_selem_id_t * sid = mixer_sid_list[ devnum ];
|
|
// The next line (hopefully) only finds selem's, not elem's.
|
|
elem = snd_mixer_find_selem(_handle, sid);
|
|
|
|
if ( elem == 0 ) {
|
|
// !! Check, whether the warning should be omitted. Probably
|
|
// Route controls are non-simple elements.
|
|
kdDebug(67100) << "Error finding mixer element " << devnum << endl;
|
|
}
|
|
}
|
|
return elem;
|
|
|
|
/*
|
|
I would have liked to use the following trivial implementation instead of the
|
|
code above. But it will also return elem's. which are not selem's. As there is
|
|
no way to check an elem's type (e.g. elem->type == SND_MIXER_ELEM_SIMPLE), callers
|
|
of getMixerElem() cannot check the type. :-(
|
|
snd_mixer_elem_t* elem = mixer_elem_list[ devnum ];
|
|
return elem;
|
|
*/
|
|
}
|
|
|
|
bool Mixer_ALSA::prepareUpdateFromHW()
|
|
{
|
|
if ( !m_fds || !m_isOpen )
|
|
return false;
|
|
|
|
//kdDebug(67100) << "Mixer_ALSA::prepareUpdate() 1\n";
|
|
|
|
// Poll on fds with 10ms timeout
|
|
// Hint: alsamixer has an infinite timeout, but we cannot do this because we would block
|
|
// the X11 event handling (Qt event loop) with this.
|
|
//kdDebug(67100) << "Mixer_ALSA::prepareUpdate() 3\n";
|
|
int finished = poll(m_fds, m_count, 10);
|
|
//kdDebug(67100) << "Mixer_ALSA::prepareUpdate() 4\n";
|
|
|
|
bool updated = false;
|
|
if (finished > 0) {
|
|
//kdDebug(67100) << "Mixer_ALSA::prepareUpdate() 5\n";
|
|
|
|
unsigned short revents;
|
|
|
|
if (snd_mixer_poll_descriptors_revents(_handle, m_fds, m_count, &revents) >= 0) {
|
|
//kdDebug(67100) << "Mixer_ALSA::prepareUpdate() 6\n";
|
|
|
|
if (revents & POLLNVAL) {
|
|
/* Bug 127294 shows, that we receieve POLLNVAL when the user
|
|
unplugs an USB soundcard. Lets close the card. */
|
|
kdDebug(67100) << "Mixer_ALSA::poll() , Error: poll() returns POLLNVAL\n";
|
|
close(); // Card was unplugged (unplug, driver unloaded)
|
|
return false;
|
|
}
|
|
if (revents & POLLERR) {
|
|
kdDebug(67100) << "Mixer_ALSA::poll() , Error: poll() returns POLLERR\n";
|
|
return false;
|
|
}
|
|
if (revents & POLLIN) {
|
|
//kdDebug(67100) << "Mixer_ALSA::prepareUpdate() 7\n";
|
|
|
|
snd_mixer_handle_events(_handle);
|
|
updated = true;
|
|
}
|
|
}
|
|
|
|
}
|
|
//kdDebug(67100) << "Mixer_ALSA::prepareUpdate() " << updated << endl;;
|
|
return updated;
|
|
}
|
|
|
|
bool
|
|
Mixer_ALSA::isRecsrcHW( int devnum )
|
|
{
|
|
bool isCurrentlyRecSrc = false;
|
|
snd_mixer_elem_t *elem = getMixerElem( devnum );
|
|
|
|
if ( !elem ) {
|
|
return false;
|
|
}
|
|
|
|
if ( snd_mixer_selem_has_capture_switch( elem ) ) {
|
|
// Has a on-off switch
|
|
// Yes, this element can be record source. But the user can switch it off, so lets see if it is switched on.
|
|
int swLeft;
|
|
int ret = snd_mixer_selem_get_capture_switch( elem, SND_MIXER_SCHN_FRONT_LEFT, &swLeft );
|
|
if ( ret != 0 ) {
|
|
kdDebug(67100) << "snd_mixer_selem_get_capture_switch() failed 1\n";
|
|
}
|
|
|
|
if (snd_mixer_selem_has_capture_switch_joined( elem ) ) {
|
|
isCurrentlyRecSrc = (swLeft != 0);
|
|
#ifdef ALSA_SWITCH_DEBUG
|
|
kdDebug(67100) << "Mixer_ALSA::isRecsrcHW() has_switch joined: #" << devnum << " >>> " << swLeft << " : " << isCurrentlyRecSrc << endl;
|
|
#endif
|
|
}
|
|
else {
|
|
int swRight;
|
|
snd_mixer_selem_get_capture_switch( elem, SND_MIXER_SCHN_FRONT_RIGHT, &swRight );
|
|
isCurrentlyRecSrc = ( (swLeft != 0) || (swRight != 0) );
|
|
#ifdef ALSA_SWITCH_DEBUG
|
|
kdDebug(67100) << "Mixer_ALSA::isRecsrcHW() has_switch non-joined, state " << isCurrentlyRecSrc << endl;
|
|
#endif
|
|
}
|
|
}
|
|
else {
|
|
// Has no on-off switch
|
|
if ( snd_mixer_selem_has_capture_volume( elem ) ) {
|
|
// Has a volume, but has no OnOffSwitch => We assume that this is a fixed record source (always on). (esken)
|
|
isCurrentlyRecSrc = true;
|
|
#ifdef ALSA_SWITCH_DEBUG
|
|
kdDebug(67100) << "Mixer_ALSA::isRecsrcHW() has_no_switch, state " << isCurrentlyRecSrc << endl;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
return isCurrentlyRecSrc;
|
|
}
|
|
|
|
bool
|
|
Mixer_ALSA::setRecsrcHW( int devnum, bool on )
|
|
{
|
|
int sw = 0;
|
|
if (on)
|
|
sw = !sw;
|
|
|
|
snd_mixer_elem_t *elem = getMixerElem( devnum );
|
|
if ( !elem )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (snd_mixer_selem_has_capture_switch_joined( elem ) )
|
|
{
|
|
int before, after;
|
|
int ret = snd_mixer_selem_get_capture_switch( elem, SND_MIXER_SCHN_FRONT_LEFT, &before );
|
|
if ( ret != 0 ) {
|
|
kdDebug(67100) << "snd_mixer_selem_get_capture_switch() failed 1\n";
|
|
}
|
|
|
|
ret = snd_mixer_selem_set_capture_switch_all( elem, sw );
|
|
if ( ret != 0 ) {
|
|
kdDebug(67100) << "snd_mixer_selem_set_capture_switch_all() failed 2: errno=" << ret << "\n";
|
|
}
|
|
ret = snd_mixer_selem_get_capture_switch( elem, SND_MIXER_SCHN_FRONT_LEFT, &after );
|
|
if ( ret != 0 ) {
|
|
kdDebug(67100) << "snd_mixer_selem_get_capture_switch() failed 3: errno=" << ret << "\n";
|
|
}
|
|
|
|
#ifdef ALSA_SWITCH_DEBUG
|
|
kdDebug(67100) << "Mixer_ALSA::setRecsrcHW(" << devnum << "," << on << ")joined. Before=" << before << " Set=" << sw << " After=" << after <<"\n";
|
|
#endif
|
|
|
|
}
|
|
else
|
|
{
|
|
#ifdef ALSA_SWITCH_DEBUG
|
|
kdDebug(67100) << "Mixer_ALSA::setRecsrcHW LEFT\n";
|
|
#endif
|
|
snd_mixer_selem_set_capture_switch( elem, SND_MIXER_SCHN_FRONT_LEFT, sw );
|
|
#ifdef ALSA_SWITCH_DEBUG
|
|
kdDebug(67100) << "Mixer_ALSA::setRecsrcHW RIGHT\n";
|
|
#endif
|
|
snd_mixer_selem_set_capture_switch(elem, SND_MIXER_SCHN_FRONT_RIGHT, sw);
|
|
}
|
|
|
|
#ifdef ALSA_SWITCH_DEBUG
|
|
kdDebug(67100) << "EXIT Mixer_ALSA::setRecsrcHW(" << devnum << "," << on << ")\n";
|
|
#endif
|
|
return false; // we should always return false, so that other devnum's get updated
|
|
// The ALSA polling has been implemented some time ago. So it should be safe to
|
|
// return "true" here.
|
|
// The other devnum's Rec-Sources won't get update by KMix code, but ALSA will send
|
|
// us an event, if neccesary. But OTOH it is possibly better not to trust alsalib fully,
|
|
// because the old code is working also well (just takes more processing time).
|
|
// return true;
|
|
}
|
|
|
|
/**
|
|
* Sets the ID of the currently selected Enum entry.
|
|
* Warning: ALSA supports to have different enums selected on each channel
|
|
* of the SAME snd_mixer_elem_t. KMix does NOT support that and
|
|
* always sets both channels (0 and 1).
|
|
*/
|
|
void Mixer_ALSA::setEnumIdHW(int mixerIdx, unsigned int idx) {
|
|
//kdDebug(67100) << "Mixer_ALSA::setEnumIdHW(" << mixerIdx << ", idx=" << idx << ") 1\n";
|
|
snd_mixer_elem_t *elem = getMixerElem( mixerIdx );
|
|
if ( elem==0 || ( !snd_mixer_selem_is_enumerated(elem)) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
//kdDebug(67100) << "Mixer_ALSA::setEnumIdHW(" << mixerIdx << ", idx=" << idx << ") 2\n";
|
|
int ret = snd_mixer_selem_set_enum_item(elem,SND_MIXER_SCHN_FRONT_LEFT,idx);
|
|
if (ret < 0) {
|
|
kdError(67100) << "Mixer_ALSA::setEnumIdHW(" << mixerIdx << "), errno=" << ret << "\n";
|
|
}
|
|
snd_mixer_selem_set_enum_item(elem,SND_MIXER_SCHN_FRONT_RIGHT,idx);
|
|
// we don't care about possible error codes on channel 1
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Return the ID of the currently selected Enum entry.
|
|
* Warning: ALSA supports to have different enums selected on each channel
|
|
* of the SAME snd_mixer_elem_t. KMix does NOT support that and
|
|
* always shows the value of the first channel.
|
|
*/
|
|
unsigned int Mixer_ALSA::enumIdHW(int mixerIdx) {
|
|
snd_mixer_elem_t *elem = getMixerElem( mixerIdx );
|
|
if ( elem==0 || ( !snd_mixer_selem_is_enumerated(elem)) )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
unsigned int idx = 0;
|
|
int ret = snd_mixer_selem_get_enum_item(elem,SND_MIXER_SCHN_FRONT_LEFT,&idx);
|
|
//kdDebug(67100) << "Mixer_ALSA::enumIdHW(" << mixerIdx << ") idx=" << idx << "\n";
|
|
if (ret < 0) {
|
|
idx = 0;
|
|
kdError(67100) << "Mixer_ALSA::enumIdHW(" << mixerIdx << "), errno=" << ret << "\n";
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
|
|
int
|
|
Mixer_ALSA::readVolumeFromHW( int mixerIdx, Volume &volume )
|
|
{
|
|
int elem_sw;
|
|
long left, right;
|
|
|
|
snd_mixer_elem_t *elem = getMixerElem( mixerIdx );
|
|
if ( !elem )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
|
|
// *** READ PLAYBACK VOLUMES *************
|
|
if ( snd_mixer_selem_has_playback_volume( elem ) && !volume.isCapture() )
|
|
{
|
|
int ret = snd_mixer_selem_get_playback_volume( elem, SND_MIXER_SCHN_FRONT_LEFT, &left );
|
|
if ( ret != 0 ) kdDebug(67100) << "readVolumeFromHW(" << mixerIdx << ") [has_playback_volume,R] failed, errno=" << ret << endl;
|
|
if ( snd_mixer_selem_is_playback_mono ( elem )) {
|
|
volume.setVolume( Volume::LEFT , left );
|
|
volume.setVolume( Volume::RIGHT, left );
|
|
}
|
|
else {
|
|
int ret = snd_mixer_selem_get_playback_volume( elem, SND_MIXER_SCHN_FRONT_RIGHT, &right );
|
|
if ( ret != 0 ) kdDebug(67100) << "readVolumeFromHW(" << mixerIdx << ") [has_playback_volume,R] failed, errno=" << ret << endl;
|
|
volume.setVolume( Volume::LEFT , left );
|
|
volume.setVolume( Volume::RIGHT, right );
|
|
}
|
|
}
|
|
else
|
|
if ( snd_mixer_selem_has_capture_volume ( elem ) && volume.isCapture() )
|
|
{
|
|
int ret = snd_mixer_selem_get_capture_volume ( elem, SND_MIXER_SCHN_FRONT_LEFT, &left );
|
|
if ( ret != 0 ) kdDebug(67100) << "readVolumeFromHW(" << mixerIdx << ") [get_capture_volume,L] failed, errno=" << ret << endl;
|
|
if ( snd_mixer_selem_is_capture_mono ( elem )) {
|
|
volume.setVolume( Volume::LEFT , left );
|
|
volume.setVolume( Volume::RIGHT, left );
|
|
}
|
|
else
|
|
{
|
|
int ret = snd_mixer_selem_get_capture_volume( elem, SND_MIXER_SCHN_FRONT_RIGHT, &right );
|
|
if ( ret != 0 ) kdDebug(67100) << "readVolumeFromHW(" << mixerIdx << ") [has_capture_volume,R] failed, errno=" << ret << endl;
|
|
volume.setVolume( Volume::LEFT , left );
|
|
volume.setVolume( Volume::RIGHT, right );
|
|
}
|
|
}
|
|
|
|
//kdDebug() << "snd_mixer_selem_has_playback_volume " << mixerIdx << " " << snd_mixer_selem_has_playback_switch( elem ) << endl;
|
|
if ( snd_mixer_selem_has_playback_switch( elem ) )
|
|
{
|
|
snd_mixer_selem_get_playback_switch( elem, SND_MIXER_SCHN_FRONT_LEFT, &elem_sw );
|
|
volume.setMuted( elem_sw == 0 );
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
Mixer_ALSA::writeVolumeToHW( int devnum, Volume& volume )
|
|
{
|
|
int left, right;
|
|
|
|
snd_mixer_elem_t *elem = getMixerElem( devnum );
|
|
if ( !elem )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// --- VOLUME - WE HAVE JUST ONE TYPE OF VOLUME A TIME,
|
|
// CAPTURE OR PLAYBACK, SO IT"S JUST USE VOLUME ------------
|
|
left = volume[ Volume::LEFT ];
|
|
right = volume[ Volume::RIGHT ];
|
|
|
|
if (snd_mixer_selem_has_playback_volume( elem ) && !volume.isCapture() ) {
|
|
snd_mixer_selem_set_playback_volume ( elem, SND_MIXER_SCHN_FRONT_LEFT, left );
|
|
if ( ! snd_mixer_selem_is_playback_mono ( elem ) )
|
|
snd_mixer_selem_set_playback_volume ( elem, SND_MIXER_SCHN_FRONT_RIGHT, right );
|
|
}
|
|
else if ( snd_mixer_selem_has_capture_volume( elem ) && volume.isCapture() ) {
|
|
snd_mixer_selem_set_capture_volume ( elem, SND_MIXER_SCHN_FRONT_LEFT, left );
|
|
if ( ! snd_mixer_selem_is_playback_mono ( elem ) )
|
|
snd_mixer_selem_set_capture_volume ( elem, SND_MIXER_SCHN_FRONT_RIGHT, right );
|
|
}
|
|
|
|
if ( snd_mixer_selem_has_playback_switch( elem ) )
|
|
{
|
|
int sw = 0;
|
|
if (! volume.isMuted())
|
|
sw = !sw;
|
|
snd_mixer_selem_set_playback_switch_all(elem, sw);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
QString
|
|
Mixer_ALSA::errorText( int mixer_error )
|
|
{
|
|
QString l_s_errmsg;
|
|
switch ( mixer_error )
|
|
{
|
|
case Mixer::ERR_PERM:
|
|
l_s_errmsg = i18n("You do not have permission to access the alsa mixer device.\n" \
|
|
"Please verify if all alsa devices are properly created.");
|
|
break;
|
|
case Mixer::ERR_OPEN:
|
|
l_s_errmsg = i18n("Alsa mixer cannot be found.\n" \
|
|
"Please check that the soundcard is installed and the\n" \
|
|
"soundcard driver is loaded.\n" );
|
|
break;
|
|
default:
|
|
l_s_errmsg = Mixer_Backend::errorText( mixer_error );
|
|
}
|
|
return l_s_errmsg;
|
|
}
|
|
|
|
|
|
QString
|
|
ALSA_getDriverName()
|
|
{
|
|
return "ALSA";
|
|
}
|
|
|
|
|
|
ALSA_DevIterator::ALSA_DevIterator()
|
|
{
|
|
N = -1;
|
|
NMax = 31;
|
|
}
|
|
|
|
void ALSA_DevIterator::next()
|
|
{
|
|
#if 0
|
|
int rc = snd_card_next(&N);
|
|
if (rc || (N == -1)) N = NMax + 1;
|
|
#else
|
|
if ((snd_card_next(&N) != 0) || (N == -1)) N = NMax + 1;
|
|
#endif
|
|
}
|
|
|
|
DevIterator* ALSA_getDevIterator()
|
|
{
|
|
return new ALSA_DevIterator();
|
|
}
|