diff --git a/backends/mixer_oss4.cpp b/backends/mixer_oss4.cpp index f24d834c..1b6509ec 100644 --- a/backends/mixer_oss4.cpp +++ b/backends/mixer_oss4.cpp @@ -1,799 +1,798 @@ /* * KMix -- KDE's full featured mini mixer * * Copyright (C) 1996-2000 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. */ //OSS4 mixer backend for KMix by Yoper Team released under GPL v2 or later /* We're getting soundcard.h via mixer_oss4.h */ #include "mixer_oss4.h" #include #include #include #include #include #include -#include #include #include Mixer_Backend* OSS4_getMixer(Mixer *mixer, int device) { Mixer_Backend *l_mixer; l_mixer = new Mixer_OSS4(mixer, device); return l_mixer; } Mixer_OSS4::Mixer_OSS4(Mixer *mixer, int device) : Mixer_Backend(mixer, device) { if ( device == -1 ) m_devnum = 0; m_numExtensions = 0; m_fd = -1; m_ossVersion = 0; m_modifyCounter = -1; } bool Mixer_OSS4::CheckCapture(oss_mixext *ext) { QString name = ext->extname; if ( ext->flags & MIXF_RECVOL || name.split('.').contains("in") ) { return true; } return false; } Mixer_OSS4::~Mixer_OSS4() { close(); } //classifies mixexts according to their name, last classification wins MixDevice::ChannelType Mixer_OSS4::classifyAndRename(QString &name, int flags) { MixDevice::ChannelType cType = MixDevice::UNKNOWN; QStringList classes = name.split (QRegExp( QLatin1String( "[-,.]" ) )); if ( flags & MIXF_PCMVOL || flags & MIXF_MONVOL || flags & MIXF_MAINVOL ) { cType = MixDevice::VOLUME; } for ( QStringList::Iterator it = classes.begin(); it != classes.end(); ++it ) { if ( *it == "line" ) { *it = "Line"; cType = MixDevice::EXTERNAL; } else if ( *it == "mic" ) { *it = "Microphone"; cType = MixDevice::MICROPHONE; } else if ( *it == "vol" ) { *it = "Volume"; cType = MixDevice::VOLUME; } else if ( *it == "surr" ) { *it = "Surround"; cType = MixDevice::SURROUND; } else if ( *it == "bass" ) { *it = "Bass"; cType = MixDevice::BASS; } else if ( *it == "treble" ) { *it = "Treble"; cType = MixDevice::TREBLE; } else if ( (*it).startsWith ( QLatin1String("pcm") ) ) { (*it).replace ( "pcm","PCM" ); cType = MixDevice::AUDIO; } else if ( *it == "src" ) { *it = "Source"; } else if ( *it == "rec" ) { *it = "Recording"; } else if ( *it == "cd" ) { *it = (*it).toUpper(); cType = MixDevice::CD; } if ( (*it).startsWith(QLatin1String("vmix")) ) { (*it).replace("vmix","Virtual Mixer"); cType = MixDevice::VOLUME; } else if ( (*it).endsWith(QLatin1String("vol")) ) { QCharRef ref = (*it)[0]; ref = ref.toUpper(); cType = MixDevice::VOLUME; } else if ( (*it).contains("speaker") ) { QCharRef ref = (*it)[0]; ref = ref.toUpper(); cType = MixDevice::SPEAKER; } else if ( (*it).contains("center") && (*it).contains("lfe")) { QCharRef ref = (*it)[0]; ref = ref.toUpper(); cType = MixDevice::SURROUND_LFE; } else if ( (*it).contains("rear") ) { QCharRef ref = (*it)[0]; ref = ref.toUpper(); cType = MixDevice::SURROUND_CENTERBACK; } else if ( (*it).contains("front") ) { QCharRef ref = (*it)[0]; ref = ref.toUpper(); cType = MixDevice::SURROUND_CENTERFRONT; } else if ( (*it).contains("headphone") ) { QCharRef ref = (*it)[0]; ref = ref.toUpper(); cType = MixDevice::HEADPHONE; } else { QCharRef ref = (*it)[0]; ref = ref.toUpper(); } } name = classes.join( QLatin1String( " " )); return cType; } int Mixer_OSS4::open() { if ( (m_fd= QT_OPEN("/dev/mixer", O_RDWR)) < 0 ) { if ( errno == EACCES ) return Mixer::ERR_PERM; else return Mixer::ERR_OPEN; } /* * Intentionally not wrapped - some systems may not support this ioctl, and therefore * aren't OSSv4. No need to throw needless error messages at the user in that case. */ if( ::ioctl (m_fd, OSS_GETVERSION, &m_ossVersion) < 0) { return Mixer::ERR_OPEN; } if (m_ossVersion < 0x040000) { return Mixer::ERR_OPEN; } wrapIoctl( ioctl (m_fd, SNDCTL_MIX_NRMIX, &m_numMixers) ); if ( m_mixDevices.isEmpty() ) { if ( m_devnum >= 0 && m_devnum < m_numMixers ) { m_numExtensions = m_devnum; bool masterChosen = false; bool masterHeuristicAvailable = false; bool saveAsMasterHeuristc = false; shared_ptr masterHeuristic; oss_mixext ext; ext.dev = m_devnum; oss_mixerinfo mi; mi.dev = m_devnum; if ( wrapIoctl( ioctl (m_fd, SNDCTL_MIXERINFO, &mi) ) < 0 ) { return Mixer::ERR_READ; } /* Mixer is disabled - this can happen, e.g. disconnected USB device */ if (!mi.enabled) { return Mixer::ERR_READ; } ::close(m_fd); if ( (m_fd= QT_OPEN(mi.devnode, O_RDWR)) < 0 ) { return Mixer::ERR_OPEN; } if ( wrapIoctl( ioctl (m_fd, SNDCTL_MIX_NREXT, &m_numExtensions) ) < 0 ) { //TO DO: more specific error handling here return Mixer::ERR_READ; } if( m_numExtensions == 0 ) { return Mixer::ERR_OPEN; } ext.ctrl = 0; //read MIXT_DEVROOT, return Mixer::NODEV on error if ( wrapIoctl ( ioctl( m_fd, SNDCTL_MIX_EXTINFO, &ext) ) < 0 ) { return Mixer::ERR_OPEN; } oss_mixext_root *root = (oss_mixext_root *) ext.data; registerCard(root->name); for ( int i = 1; i < m_numExtensions; i++ ) { bool isCapture = false; ext.dev = m_devnum; ext.ctrl = i; //wrapIoctl handles reinitialization, cancel loading on EIDRM if ( wrapIoctl( ioctl( m_fd, SNDCTL_MIX_EXTINFO, &ext) ) == EIDRM ) { return 0; } QString name = ext.extname; //skip unreadable controls if ( ext.flags & MIXF_READABLE #ifndef MIXT_MUTE && (name.contains("mute")) #endif #ifdef MIXT_MUTE && (name.contains("mute") || ext.flags == MIXT_MUTE) #endif ) { continue; } //skip all vmix controls with the exception of outvol else if ( name.contains("vmix") && ! name.contains("enable") ) { //some heuristic in case we got no exported main volume control if( name.contains("outvol") && ext.type != MIXT_ONOFF ) { saveAsMasterHeuristc = true; } else { continue; } } //fix for old legacy names, according to Hannu's suggestions if ( name.contains('_') ) { name = name.section('_',1,1).toLower(); } isCapture = CheckCapture (&ext); Volume::ChannelMask chMask = Volume::MNONE; MixDevice::ChannelType cType = classifyAndRename(name, ext.flags); if ( (ext.type == MIXT_STEREOSLIDER16 || ext.type == MIXT_STEREOSLIDER || ext.type == MIXT_MONOSLIDER16 || ext.type == MIXT_MONOSLIDER || ext.type == MIXT_SLIDER ) ) { if ( ext.type == MIXT_STEREOSLIDER16 || ext.type == MIXT_STEREOSLIDER ) { chMask = Volume::ChannelMask(Volume::MLEFT|Volume::MRIGHT); } else { chMask = Volume::MLEFT; } Volume vol (ext.maxvalue, ext.minvalue, false, isCapture); vol.addVolumeChannels(chMask); MixDevice* md_ptr = new MixDevice(_mixer, QString::number(i), name, cType); shared_ptr md = md_ptr->addToPool(); m_mixDevices.append(md); if(isCapture) { md->addCaptureVolume(vol); } else { md->addPlaybackVolume(vol); } if( saveAsMasterHeuristc && ! masterHeuristicAvailable ) { masterHeuristic = md; masterHeuristicAvailable = true; } if ( !masterChosen && ext.flags & MIXF_MAINVOL ) { m_recommendedMaster = md; masterChosen = true; } } else if ( ext.type == MIXT_HEXVALUE ) { chMask = Volume::ChannelMask(Volume::MLEFT); Volume vol (ext.maxvalue, ext.minvalue, false, isCapture); vol.addVolumeChannels(chMask); MixDevice* md_ptr = new MixDevice(_mixer, QString::number(i), name, cType); shared_ptr md = md_ptr->addToPool(); m_mixDevices.append(md); if(isCapture) { md->addCaptureVolume(vol); } else { md->addPlaybackVolume(vol); } if ( !masterChosen && ext.flags & MIXF_MAINVOL ) { m_recommendedMaster = md; masterChosen = true; } } else if ( ext.type == MIXT_ONOFF #ifdef MIXT_MUTE || ext.type == MIXT_MUTE #endif ) { Volume vol(1, 0, true, isCapture); if (isCapture) vol.setSwitchType (Volume::CaptureSwitch); else if (ext.type == MIXT_ONOFF) { vol.setSwitchType (Volume::SpecialSwitch); } MixDevice* md_ptr = new MixDevice(_mixer, QString::number(i), name, cType); shared_ptr md = md_ptr->addToPool(); m_mixDevices.append(md); if(isCapture) { md->addCaptureVolume(vol); } else { md->addPlaybackVolume(vol); } } else if ( ext.type == MIXT_ENUM ) { oss_mixer_enuminfo ei; ei.dev = m_devnum; ei.ctrl = i; if ( wrapIoctl( ioctl (m_fd, SNDCTL_MIX_ENUMINFO, &ei) ) != -1 ) { Volume vol(ext.maxvalue, ext.minvalue, false, isCapture); vol.addVolumeChannel(VolumeChannel(Volume::LEFT)); MixDevice* md_ptr = new MixDevice (_mixer, QString::number(i), name, cType); QList enumValuesRef; QString thisElement; for ( int j = 0; j < ei.nvalues; j++ ) { thisElement = &ei.strings[ ei.strindex[j] ]; if ( thisElement.isEmpty() ) { thisElement = QString::number(j); } enumValuesRef.append( new QString(thisElement) ); } md_ptr->addEnums(enumValuesRef); shared_ptr md = md_ptr->addToPool(); m_mixDevices.append(md); } } if ( ! masterChosen && masterHeuristicAvailable ) { m_recommendedMaster = masterHeuristic; } } } else { return -1; } } m_isOpen = true; return 0; } int Mixer_OSS4::close() { m_isOpen = false; int l_i_ret = ::close(m_fd); m_recommendedMaster.reset(); closeCommon(); return l_i_ret; } QString Mixer_OSS4::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" \ "Login as root and do a 'chmod a+rw /dev/mixer*' to allow the access."); break; case Mixer::ERR_OPEN: l_s_errmsg = i18n("kmix: Mixer cannot be found.\n" \ "Please check that the soundcard is installed and the\n" \ "soundcard driver is loaded.\n" \ "On Linux you might need to use 'insmod' to load the driver.\n" \ "Use 'soundon' when using OSS4 from 4front."); break; default: l_s_errmsg = Mixer_Backend::errorText(mixer_error); } return l_s_errmsg; } int Mixer_OSS4::id2num(const QString& id) { return id.toInt(); } bool Mixer_OSS4::hasChangedControls() { oss_mixerinfo minfo; minfo.dev = -1; if ( wrapIoctl( ioctl(m_fd, SNDCTL_MIXERINFO, &minfo) ) < 0 ) { qCDebug(KMIX_LOG) << "Can't get mixerinfo from card!\n"; return false; } if (!minfo.enabled) { // Mixer is disabled. Probably disconnected USB device or card is unavailable; qCDebug(KMIX_LOG) << "Mixer for card is disabled!\n"; close(); return false; } if (minfo.modify_counter == m_modifyCounter) return false; else m_modifyCounter = minfo.modify_counter; return true; } int Mixer_OSS4::readVolumeFromHW(const QString& id, shared_ptr md) { oss_mixext extinfo; oss_mixer_value mv; extinfo.dev = m_devnum; extinfo.ctrl = id2num(id); if ( wrapIoctl( ioctl(m_fd, SNDCTL_MIX_EXTINFO, &extinfo) ) < 0 ) { //TO DO: more specific error handling return Mixer::ERR_READ; } Volume &vol = (CheckCapture (&extinfo)) ? md->captureVolume() : md->playbackVolume(); mv.dev = extinfo.dev; mv.ctrl = extinfo.ctrl; mv.timestamp = extinfo.timestamp; if ( wrapIoctl ( ioctl (m_fd, SNDCTL_MIX_READ, &mv) ) < 0 ) { /* Oops, can't read mixer */ return Mixer::ERR_READ; } else { if ( md->isMuted() && extinfo.type != MIXT_ONOFF ) { return 0; } switch ( extinfo.type ) { #ifdef MIXT_MUTE case MIXT_MUTE: #endif case MIXT_ONOFF: md->setMuted(mv.value != extinfo.minvalue); break; case MIXT_MONOSLIDER: vol.setVolume(Volume::LEFT, mv.value & 0xff); break; case MIXT_STEREOSLIDER: vol.setVolume(Volume::LEFT, mv.value & 0xff); vol.setVolume(Volume::RIGHT, ( mv.value >> 8 ) & 0xff); break; case MIXT_SLIDER: vol.setVolume(Volume::LEFT, mv.value); break; case MIXT_MONOSLIDER16: vol.setVolume(Volume::LEFT, mv.value & 0xffff); break; case MIXT_STEREOSLIDER16: vol.setVolume(Volume::LEFT, mv.value & 0xffff); vol.setVolume(Volume::RIGHT, ( mv.value >> 16 ) & 0xffff); break; } } return 0; } int Mixer_OSS4::writeVolumeToHW(const QString& id, shared_ptr md) { int volume = 0; oss_mixext extinfo; oss_mixer_value mv; extinfo.dev = m_devnum; extinfo.ctrl = id2num(id); if ( wrapIoctl( ioctl(m_fd, SNDCTL_MIX_EXTINFO, &extinfo) ) < 0 ) { //TO DO: more specific error handling qCDebug(KMIX_LOG) << "failed to read info for control " << id2num(id); return Mixer::ERR_READ; } Volume &vol = (CheckCapture (&extinfo)) ? md->captureVolume() : md->playbackVolume(); switch ( extinfo.type ) { #ifdef MIXT_MUTE case MIXT_MUTE: #endif case MIXT_ONOFF: volume = (md->isMuted()) ? (extinfo.maxvalue) : (extinfo.minvalue); break; case MIXT_MONOSLIDER: volume = vol.getVolume(Volume::LEFT); break; case MIXT_STEREOSLIDER: volume = vol.getVolume(Volume::LEFT) | ( vol.getVolume(Volume::RIGHT) << 8 ); break; case MIXT_SLIDER: volume = vol.getVolume(Volume::LEFT); break; case MIXT_MONOSLIDER16: volume = vol.getVolume(Volume::LEFT); break; case MIXT_STEREOSLIDER16: volume = vol.getVolume(Volume::LEFT) | ( vol.getVolume(Volume::RIGHT) << 16 ); break; default: return -1; } mv.dev = extinfo.dev; mv.ctrl = extinfo.ctrl; mv.timestamp = extinfo.timestamp; mv.value = volume - extinfo.minvalue; if ( wrapIoctl ( ioctl (m_fd, SNDCTL_MIX_WRITE, &mv) ) < 0 ) { qCDebug(KMIX_LOG) << "error writing to control" << extinfo.extname; return Mixer::ERR_WRITE; } return 0; } void Mixer_OSS4::setEnumIdHW(const QString& id, unsigned int idx) { oss_mixext extinfo; oss_mixer_value mv; extinfo.dev = m_devnum; extinfo.ctrl = id2num(id); if ( wrapIoctl ( ioctl(m_fd, SNDCTL_MIX_EXTINFO, &extinfo) ) < 0 ) { //TO DO: more specific error handling qCDebug(KMIX_LOG) << "failed to read info for control " << id2num(id); return; } if ( extinfo.type != MIXT_ENUM ) { return; } //according to oss docs maxVal < minVal could be true - strange... unsigned int maxVal = (unsigned int) extinfo.maxvalue; unsigned int minVal = (unsigned int) extinfo.minvalue; if ( maxVal < minVal ) { int temp; temp = maxVal; maxVal = minVal; minVal = temp; } if ( idx > maxVal || idx < minVal ) idx = minVal; mv.dev = extinfo.dev; mv.ctrl = extinfo.ctrl; mv.timestamp = extinfo.timestamp; mv.value = idx; if ( wrapIoctl ( ioctl (m_fd, SNDCTL_MIX_WRITE, &mv) ) < 0 ) { /* Oops, can't write to mixer */ qCDebug(KMIX_LOG) << "error writing to control" << extinfo.extname; } } unsigned int Mixer_OSS4::enumIdHW(const QString& id) { oss_mixext extinfo; oss_mixer_value mv; extinfo.dev = m_devnum; extinfo.ctrl = id2num(id); if ( wrapIoctl ( ioctl(m_fd, SNDCTL_MIX_EXTINFO, &extinfo) ) < 0 ) { //TO DO: more specific error handling //TO DO: check whether those return values are actually possible return Mixer::ERR_READ; } if ( extinfo.type != MIXT_ENUM ) { return Mixer::ERR_READ; } mv.dev = extinfo.dev; mv.ctrl = extinfo.ctrl; mv.timestamp = extinfo.timestamp; if ( wrapIoctl ( ioctl (m_fd, SNDCTL_MIX_READ, &mv) ) < 0 ) { /* Oops, can't read mixer */ return Mixer::ERR_READ; } return mv.value; } int Mixer_OSS4::wrapIoctl(int ioctlRet) { switch( ioctlRet ) { case EIO: { qCDebug(KMIX_LOG) << "A hardware level error occurred"; break; } case EINVAL: { qCDebug(KMIX_LOG) << "Operation caused an EINVAL. You may have found a bug in kmix OSS4 module or in OSS4 itself"; break; } case ENXIO: { qCDebug(KMIX_LOG) << "Operation index out of bounds or requested device does not exist - you likely found a bug in the kmix OSS4 module"; break; } case EPERM: case EACCES: { qCDebug(KMIX_LOG) << errorText ( Mixer::ERR_PERM ); break; } case ENODEV: { qCDebug(KMIX_LOG) << "kmix received an ENODEV error - are the OSS4 drivers loaded ?"; break; } case EPIPE: case EIDRM: { reinitialize(); } } return ioctlRet; } QString Mixer_OSS4::getDriverName() { return "OSS4"; } QString OSS4_getDriverName() { return "OSS4"; }