/* * PortAudio Portable Real-Time Audio Library * Latest Version at: http://www.portaudio.com * Linux OSS Implementation by douglas repetto and Phil Burk * * Copyright (c) 1999-2000 Phil Burk * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files * (the "Software"), to deal in the Software without restriction, * including without limitation the rights to use, copy, modify, merge, * publish, distribute, sublicense, and/or sell copies of the Software, * and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * Any person wishing to distribute modifications to the Software is * requested to send the modifications to the original developer so that * they can be incorporated into the canonical version. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * */ /* Modification history: 20020621: pa_unix_oss.c split into pa_unix.c, pa_unix.h, pa_unix_oss.c by Augustus Saunders. See pa_unix.c for previous history. Pa_FlushStream added by Augustus Saunders for Solaris compatibility. PLB20021018 - Fill device info table with actual sample rates instead of wished for rates. - Allow stream to open if sample rate within 10% of desired rate. 20030630 - Thomas Richter - eliminated unused variable warnings. */ #include "pa_unix.h" #ifdef __linux__ #include #else #include /* JH20010905 */ #endif #ifndef AFMT_S16_NE #define AFMT_S16_NE Get_AFMT_S16_NE() /********************************************************************* * Some versions of OSS do not define AFMT_S16_NE. So check CPU. * PowerPC is Big Endian. X86 is Little Endian. */ int Get_AFMT_S16_NE( void ) { long testData = 1; char *ptr = (char *) &testData; int isLittle = ( *ptr == 1 ); /* Does address point to least significant byte? */ return isLittle ? AFMT_S16_LE : AFMT_S16_BE; } #endif /* AFMT_S16_NE */ /********************************************************************* * Try to open the named device. * If it opens, try to set various rates and formats and fill in * the device info structure. */ PaError Pa_QueryDevice( const char *deviceName, internalPortAudioDevice *pad ) { int result = paHostError; int tempDevHandle; int numChannels, maxNumChannels; int format; int numSampleRates; int sampleRate; int numRatesToTry; int lastRate; int ratesToTry[9] = {96000, 48000, 44100, 32000, 24000, 22050, 16000, 11025, 8000}; int i; /* douglas: we have to do this querying in a slightly different order. apparently some sound cards will give you different info based on their settings. e.g. a card might give you stereo at 22kHz but only mono at 44kHz. the correct order for OSS is: format, channels, sample rate */ if ( (tempDevHandle = open(deviceName,O_WRONLY|O_NONBLOCK)) == -1 ) { DBUG(("Pa_QueryDevice: could not open %s\n", deviceName )); return paHostError; } /* Ask OSS what formats are supported by the hardware. */ pad->pad_Info.nativeSampleFormats = 0; if (ioctl(tempDevHandle, SNDCTL_DSP_GETFMTS, &format) == -1) { ERR_RPT(("Pa_QueryDevice: could not get format info\n" )); goto error; } if( format & AFMT_U8 ) pad->pad_Info.nativeSampleFormats |= paUInt8; if( format & AFMT_S16_NE ) pad->pad_Info.nativeSampleFormats |= paInt16; /* Negotiate for the maximum number of channels for this device. PLB20010927 * Consider up to 16 as the upper number of channels. * Variable numChannels should contain the actual upper limit after the call. * Thanks to John Lazzaro and Heiko Purnhagen for suggestions. */ maxNumChannels = 0; for( numChannels = 1; numChannels <= 16; numChannels++ ) { int temp = numChannels; DBUG(("Pa_QueryDevice: use SNDCTL_DSP_CHANNELS, numChannels = %d\n", numChannels )) if(ioctl(tempDevHandle, SNDCTL_DSP_CHANNELS, &temp) < 0 ) { /* ioctl() failed so bail out if we already have stereo */ if( numChannels > 2 ) break; } else { /* ioctl() worked but bail out if it does not support numChannels. * We don't want to leave gaps in the numChannels supported. */ if( (numChannels > 2) && (temp != numChannels) ) break; DBUG(("Pa_QueryDevice: temp = %d\n", temp )) if( temp > maxNumChannels ) maxNumChannels = temp; /* Save maximum. */ } } /* The above negotiation may fail for an old driver so try this older technique. */ if( maxNumChannels < 1 ) { int stereo = 1; if(ioctl(tempDevHandle, SNDCTL_DSP_STEREO, &stereo) < 0) { maxNumChannels = 1; } else { maxNumChannels = (stereo) ? 2 : 1; } DBUG(("Pa_QueryDevice: use SNDCTL_DSP_STEREO, maxNumChannels = %d\n", maxNumChannels )) } pad->pad_Info.maxOutputChannels = maxNumChannels; DBUG(("Pa_QueryDevice: maxNumChannels = %d\n", maxNumChannels)) /* During channel negotiation, the last ioctl() may have failed. This can * also cause sample rate negotiation to fail. Hence the following, to return * to a supported number of channels. SG20011005 */ { int temp = maxNumChannels; if( temp > 2 ) temp = 2; /* use most reasonable default value */ ioctl(tempDevHandle, SNDCTL_DSP_CHANNELS, &temp); } /* FIXME - for now, assume maxInputChannels = maxOutputChannels. * Eventually do separate queries for O_WRONLY and O_RDONLY */ pad->pad_Info.maxInputChannels = pad->pad_Info.maxOutputChannels; DBUG(("Pa_QueryDevice: maxInputChannels = %d\n", pad->pad_Info.maxInputChannels)) /* Determine available sample rates by trying each one and seeing result. * OSS often supports funky rates such as 44188 instead of 44100! */ numSampleRates = 0; lastRate = 0; numRatesToTry = sizeof(ratesToTry)/sizeof(int); for (i = 0; i < numRatesToTry; i++) { sampleRate = ratesToTry[i]; if (ioctl(tempDevHandle, SNDCTL_DSP_SPEED, &sampleRate) >= 0 ) /* PLB20010817 */ { /* Use whatever rate OSS tells us. PLB20021018 */ if (sampleRate != lastRate) { DBUG(("Pa_QueryDevice: adding sample rate: %d\n", sampleRate)) pad->pad_SampleRates[numSampleRates] = (float)sampleRate; numSampleRates++; lastRate = sampleRate; } else { DBUG(("Pa_QueryDevice: dang - got sample rate %d again!\n", sampleRate)) } } } DBUG(("Pa_QueryDevice: final numSampleRates = %d\n", numSampleRates)) if (numSampleRates==0) /* HP20010922 */ { /* Desparate attempt to keep running even though no good rates found! */ ERR_RPT(("Pa_QueryDevice: no supported sample rate (or SNDCTL_DSP_SPEED ioctl call failed). Force 44100 Hz\n" )); pad->pad_SampleRates[numSampleRates++] = 44100; } pad->pad_Info.numSampleRates = numSampleRates; pad->pad_Info.sampleRates = pad->pad_SampleRates; /* use pointer to embedded array */ pad->pad_Info.name = deviceName; result = paNoError; error: /* We MUST close the handle here or we won't be able to reopen it later!!! */ close(tempDevHandle); return result; } /*******************************************************************************************/ PaError Pa_SetupDeviceFormat( int devHandle, int numChannels, int sampleRate ) { PaError result = paNoError; int tmp; /* Set format, channels, and rate in this order to keep OSS happy. */ /* Set data format. FIXME - handle more native formats. */ tmp = AFMT_S16_NE; if( ioctl(devHandle,SNDCTL_DSP_SETFMT,&tmp) == -1) { ERR_RPT(("Pa_SetupDeviceFormat: could not SNDCTL_DSP_SETFMT\n" )); return paHostError; } if( tmp != AFMT_S16_NE ) { ERR_RPT(("Pa_SetupDeviceFormat: HW does not support AFMT_S16_NE\n" )); return paHostError; } /* Set number of channels. */ tmp = numChannels; if (ioctl(devHandle, SNDCTL_DSP_CHANNELS, &numChannels) == -1) { ERR_RPT(("Pa_SetupDeviceFormat: could not SNDCTL_DSP_CHANNELS\n" )); return paHostError; } if( tmp != numChannels) { ERR_RPT(("Pa_SetupDeviceFormat: HW does not support %d channels\n", numChannels )); return paHostError; } /* Set playing frequency. */ tmp = sampleRate; if( ioctl(devHandle,SNDCTL_DSP_SPEED,&tmp) == -1) { ERR_RPT(("Pa_SetupDeviceFormat: could not SNDCTL_DSP_SPEED\n" )); return paHostError; } else if( tmp != sampleRate ) { int percentError = abs( (100 * (sampleRate - tmp)) / sampleRate ); PRINT(("Pa_SetupDeviceFormat: warning - requested sample rate = %d Hz - closest = %d\n", sampleRate, tmp )); /* Allow sample rate within 10% off of requested rate. PLB20021018 * Sometimes OSS uses a funky rate like 44188 instead of 44100. */ if( percentError > 10 ) { ERR_RPT(("Pa_SetupDeviceFormat: HW does not support %d Hz sample rate\n",sampleRate )); return paHostError; } } return result; } PaError Pa_SetupOutputDeviceFormat( int devHandle, int numChannels, int sampleRate ) { return Pa_SetupDeviceFormat(devHandle, numChannels, sampleRate); } PaError Pa_SetupInputDeviceFormat( int devHandle, int numChannels, int sampleRate ) { return Pa_SetupDeviceFormat(devHandle, numChannels, sampleRate); } /******************************************************************************************* ** Set number of fragments and size of fragments to achieve desired latency. */ static int CalcHigherLogTwo( int n ) { int log2 = 0; while( (1< 8 ) { numBuffers = (numBuffers + 1) >> 1; framesPerBuffer = framesPerBuffer << 1; } /* calculate size of buffers in bytes */ bufferSize = framesPerBuffer * channelsPerFrame * sizeof(short); /* FIXME - other sizes? */ /* Calculate next largest power of two */ powerOfTwo = CalcHigherLogTwo( bufferSize ); DBUG(("Pa_SetLatency: numBuffers = %d, framesPerBuffer = %d, powerOfTwo = %d\n", numBuffers, framesPerBuffer, powerOfTwo )); /* Encode info into a single int */ tmp=(numBuffers<<16) + powerOfTwo; if(ioctl(devHandle,SNDCTL_DSP_SETFRAGMENT,&tmp) == -1) { ERR_RPT(("Pa_SetLatency: could not SNDCTL_DSP_SETFRAGMENT\n" )); /* Don't return an error. Best to just continue and hope for the best. */ ERR_RPT(("Pa_SetLatency: numBuffers = %d, framesPerBuffer = %d, powerOfTwo = %d\n", numBuffers, framesPerBuffer, powerOfTwo )); } } /***********************************************************************/ PaTimestamp Pa_StreamTime( PortAudioStream *stream ) { internalPortAudioStream *past = (internalPortAudioStream *) stream; PaHostSoundControl *pahsc; count_info info; int delta; if( past == NULL ) return paBadStreamPtr; pahsc = (PaHostSoundControl *) past->past_DeviceData; if( pahsc->pahsc_NativeOutputBuffer ) { ioctl(pahsc->pahsc_OutputHandle, SNDCTL_DSP_GETOPTR, &info); delta = (info.bytes - pahsc->pahsc_LastPosPtr) & 0x000FFFFF; return (pahsc->pahsc_LastStreamBytes + delta) / (past->past_NumOutputChannels * sizeof(short)); } else { ioctl(pahsc->pahsc_InputHandle, SNDCTL_DSP_GETIPTR, &info); delta = (info.bytes - pahsc->pahsc_LastPosPtr) & 0x000FFFFF; return (pahsc->pahsc_LastStreamBytes + delta) / (past->past_NumInputChannels * sizeof(short)); } } void Pa_UpdateStreamTime(PaHostSoundControl *pahsc) { count_info info; int delta; /* Update current stream time (using a double so that we don't wrap around like info.bytes does) */ if( pahsc->pahsc_NativeOutputBuffer ) { ioctl(pahsc->pahsc_OutputHandle, SNDCTL_DSP_GETOPTR, &info); } else { ioctl(pahsc->pahsc_InputHandle, SNDCTL_DSP_GETIPTR, &info); } delta = (info.bytes - pahsc->pahsc_LastPosPtr) & 0x000FFFFF; pahsc->pahsc_LastStreamBytes += delta; pahsc->pahsc_LastPosPtr = info.bytes; } PaError Pa_FlushStream(int devHandle) { /* AS: This doesn't do anything under OSS; it was added for Solaris.*/ devHandle = devHandle; /* unused */ return paNoError; }