/* * 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 #include #include #include #include #include #include #include #include // Since we're guaranteed an OSS setup here, let's make life easier #if !defined(__NetBSD__) && !defined(__OpenBSD__) #include #else #include #endif #include "mixer_oss4.h" #include Mixer_Backend* OSS4_getMixer(int device) { Mixer_Backend *l_mixer; l_mixer = new Mixer_OSS4(device); return l_mixer; } Mixer_OSS4::Mixer_OSS4(int device) : Mixer_Backend(device) { if ( device == -1 ) m_devnum = 0; m_numExtensions = 0; } Mixer_OSS4::~Mixer_OSS4() { close(); } bool Mixer_OSS4::setRecsrcHW(int ctrlnum, bool on) { return true; } //dummy implementation only bool Mixer_OSS4::isRecsrcHW(int ctrlnum) { return false; } //classifies mixexts according to their name, last classification wins MixDevice::ChannelType Mixer_OSS4::classifyAndRename(TQString &name, int flags) { MixDevice::ChannelType cType = MixDevice::UNKNOWN; TQStringList classes = TQStringList::split ( TQRegExp ( "[-,.]" ), name ); if ( flags & MIXF_PCMVOL || flags & MIXF_MONVOL || flags & MIXF_MAINVOL ) { cType = MixDevice::VOLUME; } for ( TQStringList::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 ( "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).upper(); cType = MixDevice::CD; } if ( (*it).startsWith("vmix") ) { (*it).replace("vmix","Virtual Mixer"); cType = MixDevice::VOLUME; } else if ( (*it).endsWith("vol") ) { TQChar &ref = (*it).ref(0); ref = ref.upper(); cType = MixDevice::VOLUME; } else { TQChar &ref = (*it).ref(0); ref = ref.upper(); } } name = classes.join( " "); return cType; } int Mixer_OSS4::open() { if ( (m_fd= ::open("/dev/mixer", O_RDWR)) < 0 ) { if ( errno == EACCES ) return Mixer::ERR_PERM; else return Mixer::ERR_OPEN; } if (wrapIoctl( ioctl (m_fd, OSS_GETVERSION, &m_ossVersion) ) < 0) { return Mixer::ERR_READ; } if (m_ossVersion < 0x040000) { return Mixer::ERR_NOTSUPP; } 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; oss_mixext ext; ext.dev = m_devnum; if ( wrapIoctl( ioctl (m_fd, SNDCTL_MIX_NREXT, &m_numExtensions) ) < 0 ) { //TODO: more specific error handling here if ( errno == ENODEV ) return Mixer::ERR_NODEV; return Mixer::ERR_READ; } if( m_numExtensions == 0 ) { return Mixer::ERR_NODEV; } ext.ctrl = 0; //read MIXT_DEVROOT, return Mixer::NODEV on error if ( wrapIoctl ( ioctl( m_fd, SNDCTL_MIX_EXTINFO, &ext) ) < 0 ) { return Mixer::ERR_NODEV; } oss_mixext_root *root = (oss_mixext_root *) ext.data; m_mixerName = 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; } TQString name = ext.extname; //skip vmix volume controls and mute controls if ( (name.find("vmix") > -1 && name.find( "pcm") > -1) || name.find("mute") > -1 #ifdef MIXT_MUTE || (ext.type == MIXT_MUTE) #endif ) { continue; } //fix for old legacy names, according to Hannu's suggestions if ( name.contains('_') ) { name = name.section('_',1,1).lower(); } if ( ext.flags & MIXF_RECVOL || ext.flags & MIXF_MONVOL || name.find(".in") > -1 ) { isCapture = true; } 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 ) { if ( isCapture ) { chMask = Volume::ChannelMask(Volume::MLEFT|Volume::MRIGHT); } else { chMask = Volume::ChannelMask(Volume::MLEFT|Volume::MRIGHT ); } } else { if ( isCapture ) { chMask = Volume::MLEFT; } else { chMask = Volume::MLEFT; } } Volume vol (chMask, ext.maxvalue, ext.minvalue, isCapture); MixDevice* md = new MixDevice(i, vol, isCapture, true, name, cType, MixDevice::SLIDER); m_mixDevices.append(md); if ( !masterChosen && ext.flags & MIXF_MAINVOL ) { m_recommendedMaster = md; masterChosen = true; } } else if ( ext.type == MIXT_ONOFF ) { Volume vol; vol.setMuted(true); MixDevice* md = new MixDevice(i, vol, false, true, name, MixDevice::VOLUME, MixDevice::SWITCH); m_mixDevices.append(md); } 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(Volume::MLEFT, ext.maxvalue, ext.minvalue, false); MixDevice* md = new MixDevice (i, vol, false, false, name, MixDevice::UNKNOWN, MixDevice::ENUM); TQPtrList &enumValuesRef = md->enumValues(); TQString thisElement; for ( int i = 0; i < ei.nvalues; i++ ) { thisElement = &ei.strings[ ei.strindex[i] ]; if ( thisElement.isEmpty() ) { thisElement = TQString::number(i); } enumValuesRef.append( new TQString(thisElement) ); } m_mixDevices.append(md); } } } } else { return -1; } } m_isOpen = true; return 0; } int Mixer_OSS4::close() { m_isOpen = false; int l_i_ret = ::close(m_fd); m_mixDevices.clear(); return l_i_ret; } TQString Mixer_OSS4::errorText(int mixer_error) { TQString 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; case Mixer::ERR_NOTSUPP: l_s_errmsg = i18n("kmix expected an OSSv4 mixer module,\n" \ "but instead found an older version."); break; default: l_s_errmsg = Mixer_Backend::errorText(mixer_error); } return l_s_errmsg; } int Mixer_OSS4::readVolumeFromHW(int ctrlnum, Volume &vol) { oss_mixext extinfo; oss_mixer_value mv; extinfo.dev = m_devnum; extinfo.ctrl = ctrlnum; if ( wrapIoctl( ioctl(m_fd, SNDCTL_MIX_EXTINFO, &extinfo) ) < 0 ) { //TODO: more specific error handling 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; } else { if ( vol.isMuted() && extinfo.type != MIXT_ONOFF ) { return 0; } if ( vol.isCapture() ) { switch ( extinfo.type ) { case MIXT_ONOFF: vol.setMuted(mv.value != extinfo.maxvalue); 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; } } else { switch( extinfo.type ) { case MIXT_ONOFF: vol.setMuted(mv.value != extinfo.maxvalue); 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(int ctrlnum, Volume &vol) { int volume = 0; oss_mixext extinfo; oss_mixer_value mv; extinfo.dev = m_devnum; extinfo.ctrl = ctrlnum; if ( wrapIoctl( ioctl(m_fd, SNDCTL_MIX_EXTINFO, &extinfo) ) < 0 ) { //TODO: more specific error handling kdDebug ( 67100 ) << "failed to read info for control " << ctrlnum << endl; return Mixer::ERR_READ; } if ( vol.isMuted() && extinfo.type != MIXT_ONOFF ) { volume = 0; } else { switch ( extinfo.type ) { case MIXT_ONOFF: volume = (vol.isMuted()) ? (extinfo.minvalue) : (extinfo.maxvalue); break; case MIXT_MONOSLIDER: volume = vol[Volume::LEFT]; break; case MIXT_STEREOSLIDER: volume = vol[Volume::LEFT] | ( vol[Volume::RIGHT] << 8 ); break; case MIXT_SLIDER: volume = vol[Volume::LEFT]; break; case MIXT_MONOSLIDER16: volume = vol[Volume::LEFT]; break; case MIXT_STEREOSLIDER16: volume = vol[Volume::LEFT] | ( vol[Volume::RIGHT] << 16 ); break; default: return -1; } } mv.dev = extinfo.dev; mv.ctrl = extinfo.ctrl; mv.timestamp = extinfo.timestamp; mv.value = volume; if ( wrapIoctl ( ioctl (m_fd, SNDCTL_MIX_WRITE, &mv) ) < 0 ) { kdDebug ( 67100 ) << "error writing: " << endl; return Mixer::ERR_WRITE; } return 0; } void Mixer_OSS4::setEnumIdHW(int ctrlnum, unsigned int idx) { oss_mixext extinfo; oss_mixer_value mv; extinfo.dev = m_devnum; extinfo.ctrl = ctrlnum; if ( wrapIoctl ( ioctl(m_fd, SNDCTL_MIX_EXTINFO, &extinfo) ) < 0 ) { //TODO: more specific error handling kdDebug ( 67100 ) << "failed to read info for control " << ctrlnum << endl; 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 */ kdDebug ( 67100 ) << "error writing: " << endl; } } unsigned int Mixer_OSS4::enumIdHW(int ctrlnum) { oss_mixext extinfo; oss_mixer_value mv; extinfo.dev = m_devnum; extinfo.ctrl = ctrlnum; if ( wrapIoctl ( ioctl(m_fd, SNDCTL_MIX_EXTINFO, &extinfo) ) < 0 ) { //TODO: more specific error handling //TODO: 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: { kdDebug ( 67100 ) << "A hardware level error occured" << endl; break; } case EINVAL: { kdDebug ( 67100 ) << "Operation caused an EINVAL. You may have found a bug in kmix OSS4 module or in OSS4 itself" << endl; break; } case ENXIO: { kdDebug ( 67100 ) << "Operation index out of bounds or requested device does not exist - you likely found a bug in the kmix OSS4 module" << endl; break; } case EPERM: case EACCES: { kdDebug ( 67100 ) << errorText ( Mixer::ERR_PERM ) << endl; break; } case ENODEV: { kdDebug ( 67100 ) << "kmix received an ENODEV errors - are the OSS4 drivers loaded?" << endl; break; } case EPIPE: case EIDRM: { reinitialize(); } } return ioctlRet; } TQString OSS4_getDriverName() { return "OSS4"; }